From cde43e4850d7b904fcf20121188154c0bb8ca0aa Mon Sep 17 00:00:00 2001 From: Shahriyar Rzayev Date: Sun, 3 May 2020 13:58:33 +0400 Subject: [PATCH 01/30] Initial commit adding Todo models functionality --- .../Golang_Gorilla/golang_rest_api/main.go | 3 +- .../golang_rest_api/models/services.go | 12 ++- .../golang_rest_api/models/todos.go | 81 +++++++++++++++++++ 3 files changed, 93 insertions(+), 3 deletions(-) create mode 100644 Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/todos.go diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/main.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/main.go index 2f1e0db..25fff27 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/main.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/main.go @@ -22,7 +22,8 @@ func main() { dbCfg := cfg.Database services, err := models.NewServices( models.WithGorm(dbCfg.Dialect(), dbCfg.ConnectionInfo()), - models.WithUser(cfg.Pepper)) + models.WithUser(cfg.Pepper), + models.WithTodo()) must(err) defer services.Close() diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/services.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/services.go index a4fc24e..54cf94d 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/services.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/services.go @@ -26,7 +26,14 @@ func WithUser(pepper string) ServicesConfig { s.User = NewUserService(s.db, pepper) return nil } +} +// WithTodo function for activating TodoService +func WithTodo() ServicesConfig { + return func(s *Services) error { + s.Todo = NewTodoService(s.db) + return nil + } } // NewServices will loop through all passed services @@ -45,6 +52,7 @@ func NewServices(cfgs ...ServicesConfig) (*Services, error) { // there type Services struct { User UserService + Todo TodoService db *gorm.DB } @@ -56,7 +64,7 @@ func (s *Services) Close() error { // DestructiveReset drops all tables and rebuilds them // FOR DEVELOPMENT func (s *Services) DestructiveReset() error { - err := s.db.DropTableIfExists(&User{}).Error + err := s.db.DropTableIfExists(&User{}, &Todo{}).Error if err != nil { return err } @@ -65,5 +73,5 @@ func (s *Services) DestructiveReset() error { // AutoMigrate will attempt automatically migrate all tables func (s *Services) AutoMigrate() error { - return s.db.AutoMigrate(&User{}).Error + return s.db.AutoMigrate(&User{}, &Todo{}).Error } diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/todos.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/todos.go new file mode 100644 index 0000000..df0926d --- /dev/null +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/todos.go @@ -0,0 +1,81 @@ +package models + +import "github.com/jinzhu/gorm" + +type Todo struct { + gorm.Model + Title string `json:"title" validate:"required,min=5,max=20" gorm:"not null"` + Description string `json:"description" validate:"required,min=10,max=100" gorm:"not null"` + UserID uint `json:"-"` +} + +// TodoDB interface for holding all direct database related actions + +type TodoDB interface { + // Methods for querying todos + GetTodos() ([]*Todo, error) + GetTodoByID(id uint) (*Todo, error) + + // Methods for altering the todos + UpdateTodo(td *Todo) error + AddTodo(td *Todo) error + DeleteTodo(id uint) error +} + +type TodoService interface { + TodoDB +} + +// NewTodoService creating todo service here +func NewTodoService(db *gorm.DB) TodoService { + tg := &todoGorm{db} + return &todoService{ + TodoDB: tg, + } +} + +var _ TodoService = &todoService{} + +type todoService struct { + TodoDB +} + +var _ TodoDB = &todoGorm{} + +type todoGorm struct { + db *gorm.DB +} + +func (tg *todoGorm) GetTodos() ([]*Todo, error) { + var todo []*Todo + err := tg.db.Find(&todo).Error + return todo, err +} + +func (tg *todoGorm) GetTodoByID(id uint) (*Todo, error) { + var todo Todo + db := tg.db.Where("id = ?", id) + err := first(db, &todo) + return &todo, err +} + +// Update the record in todo table i.e given new todo model +func (tg *todoGorm) UpdateTodo(todo *Todo) error { + return tg.db.Save(todo).Error +} + +// Create will create provided todo and backfill data +// like the ID, CreatedAt etc. +func (tg *todoGorm) AddTodo(todo *Todo) error { + return tg.db.Create(todo).Error +} + +// Delete the user with provided ID +func (tg *todoGorm) DeleteTodo(id uint) error { + todo, err := tg.GetTodoByID(id) + if err != nil { + return err + } + tg.db.Delete(&todo) + return nil +} From 3d9ce8ef79c8a44d83ebde29d7609cfd2133d1be Mon Sep 17 00:00:00 2001 From: Shahriyar Rzayev Date: Sun, 3 May 2020 15:52:55 +0400 Subject: [PATCH 02/30] Adding todos controllers --- .../golang_rest_api/controllers/get.go | 42 +++++++++++++++++++ .../golang_rest_api/controllers/post.go | 15 +++++++ .../golang_rest_api/controllers/todos.go | 28 +++++++++++++ .../Golang_Gorilla/golang_rest_api/main.go | 8 +++- .../golang_rest_api/models/todos.go | 12 +++--- 5 files changed, 99 insertions(+), 6 deletions(-) create mode 100644 Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/todos.go diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/get.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/get.go index 7a93716..6637e32 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/get.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/get.go @@ -67,3 +67,45 @@ func (a *Users) ListSingle(w http.ResponseWriter, r *http.Request) { a.l.Println("[ERROR] serializing user", err) } } + +// ListAll handles GET requests and returns all current todos for a given user +func (t *Todos) ListAll(w http.ResponseWriter, r *http.Request) { + t.l.Println("[DEBUG] get all records") + // Get the id from request -> URL + id := getUserID(r) + + t.l.Println("[DEBUG] get record id", id) + + acc, err := t.us.GetUserByID(id) + + switch err { + case nil: + + case models.ErrNotFound: + t.l.Println("[ERROR] fetching user", err) + + w.WriteHeader(http.StatusNotFound) + utils.Respond(w, &GenericError{Message: err.Error()}) + return + default: + t.l.Println("[ERROR] fetching user", err) + + w.WriteHeader(http.StatusInternalServerError) + utils.Respond(w, &GenericError{Message: err.Error()}) + return + } + + todos, err := t.ts.GetTodos(acc) + if err != nil { + // TODO: do better error checking + t.l.Println("[ERROR] fetching todos", err) + w.WriteHeader(http.StatusInternalServerError) + utils.Respond(w, &GenericError{Message: err.Error()}) + } + + err = utils.Respond(w, todos) + if err != nil { + // we should never be here but log the error just incase + t.l.Println("[ERROR] serializing user", err) + } +} diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/post.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/post.go index 45b0d01..cf801e1 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/post.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/post.go @@ -20,3 +20,18 @@ func (a *Users) Create(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusCreated) a.l.Printf("[DEBUG] Inserting user: %#v\n", acc) } + +// Create handles POST requests to add new users +func (t *Todos) Create(w http.ResponseWriter, r *http.Request) { + // fetch the user from the context + todo := r.Context().Value(KeyTodo{}).(*models.Todo) + err := t.ts.AddTodo(todo) + if err != nil { + t.l.Println("[ERROR] Something went wrong with user creation", err) + w.WriteHeader(http.StatusBadRequest) + utils.Respond(w, &GenericError{Message: "Something went wrong with user creation"}) + return + } + w.WriteHeader(http.StatusCreated) + t.l.Printf("[DEBUG] Inserting todo: %#v\n", todo) +} diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/todos.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/todos.go new file mode 100644 index 0000000..188ee77 --- /dev/null +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/todos.go @@ -0,0 +1,28 @@ +package controllers + +import ( + "golang_restful_api/models" + "log" +) + +// KeyUser is a key used for the Todo object in the context +type KeyTodo struct{} + +// Users handler/controller for getting and updating users +type Todos struct { + l *log.Logger + v *models.Validation + ts models.TodoService + us models.UserService +} + +// NewUsers returns a new users handler with the given logger +func NewTodos(l *log.Logger, v *models.Validation, + ts models.TodoService, us models.UserService) *Todos { + return &Todos{ + l: l, + v: v, + ts: ts, + us: us, + } +} diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/main.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/main.go index 25fff27..d066b01 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/main.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/main.go @@ -34,8 +34,12 @@ func main() { v := models.NewValidation() us := services.User - // create the user handler/conroller + l_todo := log.New(os.Stdout, "todos-api -> ", log.LstdFlags) + ts := services.Todo + + // create the handler/conrollers ah := controllers.NewUsers(l, v, us) + th := controllers.NewTodos(l_todo, v, ts, us) // Create new serve mux and register handlers r := mux.NewRouter() @@ -46,9 +50,11 @@ func main() { getR := apiV1.Methods("GET").Subrouter() getR.HandleFunc("/users", ah.ListAll) getR.HandleFunc("/users/{id:[0-9]+}", ah.ListSingle) + getR.HandleFunc("/users/{id:[0-9]+}/todos", th.ListAll) postR := apiV1.Methods("POST").Subrouter() postR.HandleFunc("/users", ah.Create) + postR.HandleFunc("/users/{id:[0-9]+}/todos", th.Create) postR.Use(ah.MiddlewareValidateUser) putR := apiV1.Methods("PUT").Subrouter() diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/todos.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/todos.go index df0926d..0808c2a 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/todos.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/todos.go @@ -13,7 +13,7 @@ type Todo struct { type TodoDB interface { // Methods for querying todos - GetTodos() ([]*Todo, error) + GetTodos(user *User) ([]*Todo, error) GetTodoByID(id uint) (*Todo, error) // Methods for altering the todos @@ -46,10 +46,12 @@ type todoGorm struct { db *gorm.DB } -func (tg *todoGorm) GetTodos() ([]*Todo, error) { - var todo []*Todo - err := tg.db.Find(&todo).Error - return todo, err +func (tg *todoGorm) GetTodos(user *User) ([]*Todo, error) { + var todos []*Todo + var todo *Todo + err := tg.db.Model(&user).Related(&todo).Find(&todos).Error + // err := tg.db.Find(&todo).Error + return todos, err } func (tg *todoGorm) GetTodoByID(id uint) (*Todo, error) { From b9e7705eedc8d03b64f6127800d4a8914d67ff86 Mon Sep 17 00:00:00 2001 From: Shahriyar Rzayev Date: Mon, 4 May 2020 19:53:49 +0400 Subject: [PATCH 03/30] Adding todo middleware --- .../golang_rest_api/controllers/middleware.go | 35 +++++++++++++++++++ .../golang_rest_api/controllers/post.go | 4 +-- .../Golang_Gorilla/golang_rest_api/main.go | 3 +- 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/middleware.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/middleware.go index 66da548..63fa806 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/middleware.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/middleware.go @@ -50,3 +50,38 @@ func (a *Users) MiddlewareValidateUser(next http.Handler) http.Handler { next.ServeHTTP(w, r) }) } + +// MiddlewareValidateTodo validates the todo in the request and calls next if ok +func (t *Todos) MiddlewareValidateTodo(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + todo := &models.Todo{} + + err := models.FromJSON(todo, r.Body) + if err != nil { + t.l.Println("[ERROR] deserializing todo", err) + + w.WriteHeader(http.StatusBadRequest) + utils.Respond(w, &GenericError{Message: err.Error()}) + return + } + + // validate the todo + errs := t.v.Validate(todo) + t.l.Println(errs.Errors()) + if len(errs) != 0 { + t.l.Println("[ERROR] validating todo", errs) + + // return the validation messages as an array + w.WriteHeader(http.StatusBadRequest) + utils.Respond(w, &ValidationError{Messages: errs.Errors()}) + return + } + + // add the todo to the context + ctx := context.WithValue(r.Context(), KeyTodo{}, todo) + r = r.WithContext(ctx) + + // Call the next handler, which can be another middleware in the chain, or the final handler. + next.ServeHTTP(w, r) + }) +} diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/post.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/post.go index cf801e1..83ee853 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/post.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/post.go @@ -27,9 +27,9 @@ func (t *Todos) Create(w http.ResponseWriter, r *http.Request) { todo := r.Context().Value(KeyTodo{}).(*models.Todo) err := t.ts.AddTodo(todo) if err != nil { - t.l.Println("[ERROR] Something went wrong with user creation", err) + t.l.Println("[ERROR] Something went wrong with todo creation", err) w.WriteHeader(http.StatusBadRequest) - utils.Respond(w, &GenericError{Message: "Something went wrong with user creation"}) + utils.Respond(w, &GenericError{Message: "Something went wrong with todo creation"}) return } w.WriteHeader(http.StatusCreated) diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/main.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/main.go index d066b01..4b602a3 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/main.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/main.go @@ -54,8 +54,9 @@ func main() { postR := apiV1.Methods("POST").Subrouter() postR.HandleFunc("/users", ah.Create) - postR.HandleFunc("/users/{id:[0-9]+}/todos", th.Create) postR.Use(ah.MiddlewareValidateUser) + postR.HandleFunc("/users/{id:[0-9]+}/todos", th.Create) + postR.Use(th.MiddlewareValidateTodo) putR := apiV1.Methods("PUT").Subrouter() putR.HandleFunc("/users/{id:[0-9]+}", ah.Update) From 8880006fefed5c97ecd0a3ef7800287a630d7cc5 Mon Sep 17 00:00:00 2001 From: Shahriyar Rzayev Date: Tue, 5 May 2020 19:43:45 +0400 Subject: [PATCH 04/30] Added TODO for changing middleware logic --- Project1_Simple_REST_API/.gitignore | 1 + .../golang_rest_api/controllers/middleware.go | 3 + .../Golang_Gorilla/golang_rest_api/go.mod | 7 +- .../Golang_Gorilla/golang_rest_api/go.sum | 413 +----------------- 4 files changed, 23 insertions(+), 401 deletions(-) create mode 100644 Project1_Simple_REST_API/.gitignore diff --git a/Project1_Simple_REST_API/.gitignore b/Project1_Simple_REST_API/.gitignore new file mode 100644 index 0000000..62c8935 --- /dev/null +++ b/Project1_Simple_REST_API/.gitignore @@ -0,0 +1 @@ +.idea/ \ No newline at end of file diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/middleware.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/middleware.go index 63fa806..64ddb49 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/middleware.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/middleware.go @@ -16,6 +16,9 @@ func CommonMiddleware(next http.Handler) http.Handler { }) } +// TODO: make single point validation middleware and call related specific validation from there +// func MiddlewareValidate(opts ...) + // MiddlewareValidateUser validates the user in the request and calls next if ok func (a *Users) MiddlewareValidateUser(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/go.mod b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/go.mod index 0fab542..5a63aba 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/go.mod +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/go.mod @@ -3,12 +3,15 @@ module golang_restful_api go 1.14 require ( - github.com/go-openapi/runtime v0.19.15 + github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-playground/universal-translator v0.17.0 // indirect github.com/go-playground/validator v9.31.0+incompatible github.com/gorilla/mux v1.7.4 github.com/jinzhu/gorm v1.9.12 + github.com/kr/pretty v0.1.0 // indirect github.com/leodido/go-urn v1.2.0 // indirect - github.com/nicholasjackson/env v0.6.0 golang.org/x/crypto v0.0.0-20200420201142-3c4aac89819a + gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect + gopkg.in/go-playground/assert.v1 v1.2.1 // indirect + gopkg.in/yaml.v2 v2.2.4 // indirect ) diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/go.sum b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/go.sum index cd5b605..41961a7 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/go.sum +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/go.sum @@ -1,451 +1,66 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= -github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= -github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= -github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= -github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA= -github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= -github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496 h1:zV3ejI06GQ59hwDQAvmK1qxOQGB3WuVTRoY0okPTAv0= -github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/corbym/gocrest v1.0.3/go.mod h1:maVFL5lbdS2PgfOQgGRWDYTeunSWQeiEgoNdTABShCs= -github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= -github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-oidc v2.2.1+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= -github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd h1:83Wprp6ROGeiHFAP8WJdI2RoxALQYgdllERc3N5N2DM= github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= -github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y= github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= -github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= -github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= -github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= -github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= -github.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk= -github.com/go-openapi/analysis v0.19.4/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk= -github.com/go-openapi/analysis v0.19.5 h1:8b2ZgKfKIUTVQpTb77MoRDIMEIwvDVw40o3aOXdfYzI= -github.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2rCu0v0ObL0AU= -github.com/go-openapi/analysis v0.19.10 h1:5BHISBAXOc/aJK25irLZnx2D3s6WyYaY9D4gmuz9fdE= -github.com/go-openapi/analysis v0.19.10/go.mod h1:qmhS3VNFxBlquFJ0RGoDtylO9y4pgTAUNE9AEEMdlJQ= -github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= -github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= -github.com/go-openapi/errors v0.19.2 h1:a2kIyV3w+OS3S97zxUndRVD46+FhGOUBDFY7nmu4CsY= -github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= -github.com/go-openapi/errors v0.19.3/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= -github.com/go-openapi/errors v0.19.4 h1:fSGwO1tSYHFu70NKaWJt5Qh0qoBRtCm/mXS1yhf+0W0= -github.com/go-openapi/errors v0.19.4/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= -github.com/go-openapi/inflect v0.19.0 h1:9jCH9scKIbHeV9m12SmPilScz6krDxKRasNNSNPXu/4= -github.com/go-openapi/inflect v0.19.0/go.mod h1:lHpZVlpIQqLyKwJ4N+YSc9hchQy/i12fJykb83CRBH4= -github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= -github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= -github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= -github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w= -github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= -github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= -github.com/go-openapi/jsonreference v0.19.2 h1:o20suLFB4Ri0tuzpWtyHlh7E7HnkqTNLq6aR6WVNS1w= -github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= -github.com/go-openapi/jsonreference v0.19.3 h1:5cxNfTy0UVC3X8JL5ymxzyoUZmo8iZb+jeTWn7tUa8o= -github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= -github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= -github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= -github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= -github.com/go-openapi/loads v0.19.2/go.mod h1:QAskZPMX5V0C2gvfkGZzJlINuP7Hx/4+ix5jWFxsNPs= -github.com/go-openapi/loads v0.19.3 h1:jwIoahqCmaA5OBoc/B+1+Mu2L0Gr8xYQnbeyQEo/7b0= -github.com/go-openapi/loads v0.19.3/go.mod h1:YVfqhUCdahYwR3f3iiwQLhicVRvLlU/WO5WPaZvcvSI= -github.com/go-openapi/loads v0.19.4/go.mod h1:zZVHonKd8DXyxyw4yfnVjPzBjIQcLt0CCsn0N0ZrQsk= -github.com/go-openapi/loads v0.19.5 h1:jZVYWawIQiA1NBnHla28ktg6hrcfTHsCE+3QLVRBIls= -github.com/go-openapi/loads v0.19.5/go.mod h1:dswLCAdonkRufe/gSUC3gN8nTSaB9uaS2es0x5/IbjY= -github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA= -github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64= -github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4= -github.com/go-openapi/runtime v0.19.11/go.mod h1:dhGWCTKRXlAfGnQG0ONViOZpjfg0m2gUt9nTQPQZuoo= -github.com/go-openapi/runtime v0.19.12 h1:5lQlCr1HL0ZVRPL+0PHKhRpMCSPMV2qF842rhQx8CYY= -github.com/go-openapi/runtime v0.19.12/go.mod h1:dhGWCTKRXlAfGnQG0ONViOZpjfg0m2gUt9nTQPQZuoo= -github.com/go-openapi/runtime v0.19.15 h1:2GIefxs9Rx1vCDNghRtypRq+ig8KSLrjHbAYI/gCLCM= -github.com/go-openapi/runtime v0.19.15/go.mod h1:dhGWCTKRXlAfGnQG0ONViOZpjfg0m2gUt9nTQPQZuoo= -github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= -github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= -github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY= -github.com/go-openapi/spec v0.19.3 h1:0XRyw8kguri6Yw4SxhsQA/atC88yqrk0+G4YhI2wabc= -github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= -github.com/go-openapi/spec v0.19.6/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk= -github.com/go-openapi/spec v0.19.7 h1:0xWSeMd35y5avQAThZR2PkEuqSosoS5t6gDH4L8n11M= -github.com/go-openapi/spec v0.19.7/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk= -github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= -github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= -github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY= -github.com/go-openapi/strfmt v0.19.2/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= -github.com/go-openapi/strfmt v0.19.3 h1:eRfyY5SkaNJCAwmmMcADjY31ow9+N7MCLW7oRkbsINA= -github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= -github.com/go-openapi/strfmt v0.19.4/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk= -github.com/go-openapi/strfmt v0.19.5 h1:0utjKrw+BAh8s57XE9Xz8DUBsVvPmRUB6styvl9wWIM= -github.com/go-openapi/strfmt v0.19.5/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk= -github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= -github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= -github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY= -github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.19.7/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY= -github.com/go-openapi/swag v0.19.8 h1:vfK6jLhs7OI4tAXkvkooviaE1JEPcw3mutyegLHHjmk= -github.com/go-openapi/swag v0.19.8/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY= -github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= -github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= -github.com/go-openapi/validate v0.19.3 h1:PAH/2DylwWcIU1s0Y7k3yNmeAgWOcKrNE2Q7Ww/kCg4= -github.com/go-openapi/validate v0.19.3/go.mod h1:90Vh6jjkTn+OT1Eefm0ZixWNFjhtOH7vS9k0lo6zwJo= -github.com/go-openapi/validate v0.19.7 h1:fR4tP2xc+25pdo5Qvv4v6g+5QKFgNg8nrifTE7V8ibA= -github.com/go-openapi/validate v0.19.7/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4= -github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/validator v9.31.0+incompatible h1:UA72EPEogEnq76ehGdEDp4Mit+3FDh548oRqwVgNsHA= github.com/go-playground/validator v9.31.0+incompatible/go.mod h1:yrEkQXlcI+PugkyDjY2bRrL/UBU4f3rvrgkN3V8JEig= -github.com/go-playground/validator/v10 v10.1.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= +github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= -github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-swagger/go-swagger v0.23.0 h1:WrzNj+tp0pK8B5K5Sf4Rab0xvrIINiX0rrgqvW0fY0k= -github.com/go-swagger/go-swagger v0.23.0/go.mod h1:5AaV4Dx69cUjpFRTZnSHPr1Y7dKBVk6SvfIvkTEqwJs= -github.com/go-swagger/scan-repo-boundary v0.0.0-20180623220736-973b3573c013/go.mod h1:b65mBPzqzZWxOZGxSWrqs4GInLIn+u99Q9q7p+GKni0= -github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= -github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= -github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg= -github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= -github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= -github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs= -github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= -github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= -github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk= -github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28= -github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo= -github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk= -github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw= -github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360= -github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg= -github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE= -github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8= -github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= -github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= -github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= -github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= -github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ= -github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0= -github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= -github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/handlers v1.4.2 h1:0QniY0USkHQ1RGCLfKxeNHK9bkDHGRYGNDFBCS+YARg= -github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= -github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc= github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA= -github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jinzhu/gorm v1.9.12 h1:Drgk1clyWT9t9ERbzHza6Mj/8FY/CqMyVzOiHviMo6Q= github.com/jinzhu/gorm v1.9.12/go.mod h1:vhTjlKSJUTWNtcbQtrMBFCxy7eXTzeCAzfL5fBZT/Qs= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.0.1 h1:HjfetcXq097iXP0uoPCdnM4Efp5/9MsM0/M+XOTeR3M= github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= -github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= -github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= -github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= -github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= -github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= -github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= -github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4= github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= -github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e h1:hB2xlXdHp/pmPZq0y3QnmWAArdw9PqbmotexnWx/FU8= -github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= -github.com/mailru/easyjson v0.7.1 h1:mdxE1MF9o53iCb2Ghj1VfWvh7ZOwHpnVG/xwXrV90U8= -github.com/mailru/easyjson v0.7.1/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= -github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= -github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= +github.com/mattn/go-sqlite3 v2.0.1+incompatible h1:xQ15muvnzGBHpIpdrNi1DA5x0+TcBZzsIDwmw9uTHzw= github.com/mattn/go-sqlite3 v2.0.1+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.2.2 h1:dxe5oCinTXiTIcfgmZecdCzPmAJKd46KsCWc35r0TV4= -github.com/mitchellh/mapstructure v1.2.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/nicholasjackson/env v0.6.0 h1:6xdio52m7cKRtgZPER6NFeBZxicR88rx5a+5Jl4/qus= -github.com/nicholasjackson/env v0.6.0/go.mod h1:/GtSb9a/BDUCLpcnpauN0d/Bw5ekSI1vLC1b9Lw0Vyk= -github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= -github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= -github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo= -github.com/pelletier/go-toml v1.6.0 h1:aetoXYr0Tv7xRU/V4B4IZJ2QcbtMUFoNb3ORp7TzIK4= -github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys= -github.com/pelletier/go-toml v1.7.0 h1:7utD74fnzVc/cpcyy8sjrlFr5vYpypUixARcHIMIGuI= -github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= -github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= -github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= -github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= -github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= -github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.6.2 h1:7aKfF+e8/k68gda3LOjo5RxiUqddoFxVq4BKBPrxk5E= -github.com/spf13/viper v1.6.2/go.mod h1:t3iDnF5Jlj76alVNuyFBk5oUMCvsrkbvZK0WQdfDi5k= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= -github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= -github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= -github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/toqueteos/webbrowser v1.2.0 h1:tVP/gpK69Fx+qMJKsLE7TD8LuGWPnEV71wBN9rrstGQ= -github.com/toqueteos/webbrowser v1.2.0/go.mod h1:XWoZq4cyp9WeUeak7w7LXRUQf1F1ATJMir8RTqb4ayM= -github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= -github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= -github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= -github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= -github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/zmb3/gogetdoc v0.0.0-20190228002656-b37376c5da6a/go.mod h1:ofmGw6LrMypycsiWcyug6516EXpIxSbZ+uI9ppGypfY= -go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= -go.mongodb.org/mongo-driver v1.1.1 h1:Sq1fR+0c58RME5EoqKdjkiQAmPjmfHlZOoRI6fTUOcs= -go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= -go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= -go.mongodb.org/mongo-driver v1.3.0/go.mod h1:MSWZXKOynuguX+JSvwP8i+58jYCXxbia8HS3gZBapIE= -go.mongodb.org/mongo-driver v1.3.1 h1:op56IfTQiaY2679w922KVWa3qcHdml2K/Io8ayAOUEQ= -go.mongodb.org/mongo-driver v1.3.1/go.mod h1:MSWZXKOynuguX+JSvwP8i+58jYCXxbia8HS3gZBapIE= -go.mongodb.org/mongo-driver v1.3.2 h1:IYppNjEV/C+/3VPbhHVxQ4t04eVW0cLp0/pNdW++6Ug= -go.mongodb.org/mongo-driver v1.3.2/go.mod h1:MSWZXKOynuguX+JSvwP8i+58jYCXxbia8HS3gZBapIE= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= -golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd h1:GGJVjV8waZKRHrgwvtH66z9ZGVurTD1MT0n1Bb+q4aM= golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200420201142-3c4aac89819a h1:y6sBfNd1b9Wy08a6K1Z1DZc4aXABUN5TKjkYhz7UKmo= golang.org/x/crypto v0.0.0-20200420201142-3c4aac89819a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM= -golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 h1:uYVVQ9WP/Ds2ROhcaGPeIdVq0RIXVLwsHlnvJ+cT1So= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200408040146-ea54a3c99b9b h1:h03Ur1RlPrGTjua4koYdpGl8W0eYo8p1uI9w7RPlkdk= -golang.org/x/sys v0.0.0-20200408040146-ea54a3c99b9b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181207195948-8634b1ecd393/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200125223703-d33eef8e6825/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200313205530-4303120df7d8 h1:gkI/wGGwpcG5W4hLCzZNGxA4wzWBGGDStRI1MrjDl2Q= -golang.org/x/tools v0.0.0-20200313205530-4303120df7d8/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200408132156-9ee5ef7a2c0d h1:2DXIdtvIYvvWOcAOsX81FwOUBoQoMZhosWn7KjXEl94= -golang.org/x/tools v0.0.0-20200408132156-9ee5ef7a2c0d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= -gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.54.0 h1:oM5ElzbIi7gwLnNbPX2M25ED1vSAK3B6dex50eS/6Fs= -gopkg.in/ini.v1 v1.54.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.55.0 h1:E8yzL5unfpW3M6fz/eB7Cb5MQAYSZ7GKo4Qth+N2sgQ= -gopkg.in/ini.v1 v1.55.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= -gopkg.in/square/go-jose.v2 v2.4.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= From 79ec0d4a733593730e5cf2e998abdec352b05685 Mon Sep 17 00:00:00 2001 From: Shahriyar Rzayev Date: Wed, 6 May 2020 15:11:55 +0400 Subject: [PATCH 05/30] Committing non-working code --- .gitignore | 1 + .../Golang_Gorilla/golang_rest_api/config.go | 2 +- .../golang_rest_api/controllers/middleware.go | 52 +++++++++++++++++++ .../Golang_Gorilla/golang_rest_api/main.go | 17 +++++- 4 files changed, 69 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 418fdb7..649a30a 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ Episode_*/ +.idea diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/config.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/config.go index e46205a..2d35327 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/config.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/config.go @@ -18,7 +18,7 @@ type PostgresConfig struct { DBName string `json:"dbname"` } -// Dialect returnin for DB dialect +// Dialect returning for DB dialect func (c PostgresConfig) Dialect() string { return "postgres" } diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/middleware.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/middleware.go index 64ddb49..0f647d1 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/middleware.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/middleware.go @@ -3,11 +3,24 @@ package controllers import ( "context" "net/http" + "reflect" + "regexp" + "fmt" "golang_restful_api/models" "golang_restful_api/utils" ) +//type fn func(interface{}, http.Handler) http.Handler + +//type usF = func(*interface{}, http.Handler) http.Handler +//type tdF = func(*interface{}, http.Handler) http.Handler + +var uRLPathRegex = map[string]interface{}{ + "^/api/v1/users$": (*Users).MiddlewareValidateUser, + "^/api/v1/users/(?P[0-9]+)/todos$": (*Todos).MiddlewareValidateTodo, + } + // CommonMiddleware for updating default content type for our router func CommonMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -18,6 +31,45 @@ func CommonMiddleware(next http.Handler) http.Handler { // TODO: make single point validation middleware and call related specific validation from there // func MiddlewareValidate(opts ...) +func MiddlewareValidate(next http.Handler) http.Handler{ + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var getURLPath = r.URL.Path + fmt.Println(getURLPath) + fmt.Println(reflect.TypeOf(Users{}).String()) + for patt, fn := range uRLPathRegex { + match, _ := regexp.MatchString(patt, getURLPath) + if match { + fmt.Println("hello", fn) + Invoke(fn, next) + } + + } + next.ServeHTTP(w, r) + }) +} + +func Invoke(fn interface{}, args ...interface{}) { + //v := reflect.ValueOf(fn) + //v.Call(reflect.Value(args[0])) + + v := reflect.ValueOf(fn) + fmt.Println(reflect.TypeOf(v)) + fmt.Println(reflect.TypeOf(fn)) + fmt.Println(args) + fmt.Println(reflect.TypeOf(args)) + fmt.Println(len(args)) + + //rArgs := []reflect.Value{reflect.ValueOf(args)} + //rArgs := make([]reflect.Value, 1) + rArgs := []reflect.Value{reflect.ValueOf(args)} + fmt.Println(rArgs) + //for i, a := range args { + // rArgs[i] = reflect.ValueOf(a) + //} + fmt.Println(rArgs) + //rArgs[0] = args[0] + v.Call(rArgs) +} // MiddlewareValidateUser validates the user in the request and calls next if ok func (a *Users) MiddlewareValidateUser(next http.Handler) http.Handler { diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/main.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/main.go index 4b602a3..d1fc77f 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/main.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/main.go @@ -9,6 +9,7 @@ import ( "os" "time" + "fmt" "github.com/gorilla/mux" ) @@ -54,9 +55,21 @@ func main() { postR := apiV1.Methods("POST").Subrouter() postR.HandleFunc("/users", ah.Create) - postR.Use(ah.MiddlewareValidateUser) + err = r.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error { + pathTemplate, err := route.GetPathTemplate() + if err == nil { + fmt.Println("ROUTE:", pathTemplate) + } + pathRegexp, err := route.GetPathRegexp() + if err == nil { + fmt.Println("Path regexp:", pathRegexp) + } + return nil + }) + //postR.Use(ah.MiddlewareValidateUser) postR.HandleFunc("/users/{id:[0-9]+}/todos", th.Create) - postR.Use(th.MiddlewareValidateTodo) + //postR.Use(th.MiddlewareValidateTodo) + postR.Use(controllers.MiddlewareValidate) putR := apiV1.Methods("PUT").Subrouter() putR.HandleFunc("/users/{id:[0-9]+}", ah.Update) From b5c2a9ef7b78feff5e2dc1731a25485cdb558f80 Mon Sep 17 00:00:00 2001 From: Shahriyar Rzayev Date: Wed, 6 May 2020 22:11:07 +0400 Subject: [PATCH 06/30] Thanks God now general validation actions are working - Able to add and query Todos --- .../golang_rest_api/controllers/middleware.go | 200 +++++++++--------- .../golang_rest_api/controllers/post.go | 5 + .../Golang_Gorilla/golang_rest_api/main.go | 22 +- .../golang_rest_api/models/services.go | 17 +- .../golang_rest_api/models/todos.go | 10 +- 5 files changed, 122 insertions(+), 132 deletions(-) diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/middleware.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/middleware.go index 0f647d1..a3e3c72 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/middleware.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/middleware.go @@ -3,7 +3,6 @@ package controllers import ( "context" "net/http" - "reflect" "regexp" "fmt" @@ -11,15 +10,23 @@ import ( "golang_restful_api/utils" ) -//type fn func(interface{}, http.Handler) http.Handler +//type genF = func(*GenHandler, http.Handler) http.Handler -//type usF = func(*interface{}, http.Handler) http.Handler -//type tdF = func(*interface{}, http.Handler) http.Handler +//type tdF func(*Todos, http.Handler) http.Handler +type GenHandler struct { + *Users + *Todos +} + +//var uRLPathRegex = map[string]interface{}{ +// "^/api/v1/users$": (*GenHandler).MiddlewareValidateUser, +// //"^/api/v1/users/(?P[0-9]+)/todos$": (*GenHandler).MiddlewareValidateTodo, +// } -var uRLPathRegex = map[string]interface{}{ - "^/api/v1/users$": (*Users).MiddlewareValidateUser, - "^/api/v1/users/(?P[0-9]+)/todos$": (*Todos).MiddlewareValidateTodo, - } +var uRLPathRegex = map[string]string{ + "^/api/v1/users$": "users", + "^/api/v1/users/(?P[0-9]+)/todos$": "todos", +} // CommonMiddleware for updating default content type for our router func CommonMiddleware(next http.Handler) http.Handler { @@ -29,114 +36,97 @@ func CommonMiddleware(next http.Handler) http.Handler { }) } -// TODO: make single point validation middleware and call related specific validation from there -// func MiddlewareValidate(opts ...) -func MiddlewareValidate(next http.Handler) http.Handler{ +// MiddlewareValidate general middleware method for calling specific validations based on path. +func (g *GenHandler) MiddlewareValidate(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var getURLPath = r.URL.Path fmt.Println(getURLPath) - fmt.Println(reflect.TypeOf(Users{}).String()) - for patt, fn := range uRLPathRegex { + for patt, val := range uRLPathRegex { match, _ := regexp.MatchString(patt, getURLPath) - if match { - fmt.Println("hello", fn) - Invoke(fn, next) + if match && val == "users" { + g.MiddlewareValidateUser(next, w, r) + break + } else { + g.MiddlewareValidateTodo(next, w, r) + break } - } - next.ServeHTTP(w, r) }) } -func Invoke(fn interface{}, args ...interface{}) { - //v := reflect.ValueOf(fn) - //v.Call(reflect.Value(args[0])) - - v := reflect.ValueOf(fn) - fmt.Println(reflect.TypeOf(v)) - fmt.Println(reflect.TypeOf(fn)) - fmt.Println(args) - fmt.Println(reflect.TypeOf(args)) - fmt.Println(len(args)) - - //rArgs := []reflect.Value{reflect.ValueOf(args)} - //rArgs := make([]reflect.Value, 1) - rArgs := []reflect.Value{reflect.ValueOf(args)} - fmt.Println(rArgs) - //for i, a := range args { - // rArgs[i] = reflect.ValueOf(a) - //} - fmt.Println(rArgs) - //rArgs[0] = args[0] - v.Call(rArgs) -} - // MiddlewareValidateUser validates the user in the request and calls next if ok -func (a *Users) MiddlewareValidateUser(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - acc := &models.User{} - - err := models.FromJSON(acc, r.Body) - if err != nil { - a.l.Println("[ERROR] deserializing user", err) - - w.WriteHeader(http.StatusBadRequest) - utils.Respond(w, &GenericError{Message: err.Error()}) - return - } - - // validate the user - errs := a.v.Validate(acc) - a.l.Println(errs.Errors()) - if len(errs) != 0 { - a.l.Println("[ERROR] validating user", errs) - - // return the validation messages as an array - w.WriteHeader(http.StatusBadRequest) - utils.Respond(w, &ValidationError{Messages: errs.Errors()}) - return - } - - // add the user to the context - ctx := context.WithValue(r.Context(), KeyUser{}, acc) - r = r.WithContext(ctx) - - // Call the next handler, which can be another middleware in the chain, or the final handler. - next.ServeHTTP(w, r) - }) +func (a *GenHandler) MiddlewareValidateUser(next http.Handler, w http.ResponseWriter, r *http.Request) error { + fmt.Println("Inside validator") + // return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + acc := &models.User{} + fmt.Println("Inside middleware 2") + + fmt.Println(r.Body) + + err := models.FromJSON(acc, r.Body) + if err != nil { + a.Users.l.Println("[ERROR] deserializing user", err) + + w.WriteHeader(http.StatusBadRequest) + utils.Respond(w, &GenericError{Message: err.Error()}) + return err + } + + // validate the user + errs := a.Users.v.Validate(acc) + a.Users.l.Println("Here: ", errs.Errors()) + if len(errs) != 0 { + a.Users.l.Println("[ERROR] validating user", errs) + + // return the validation messages as an array + w.WriteHeader(http.StatusBadRequest) + utils.Respond(w, &ValidationError{Messages: errs.Errors()}) + return err + } + + // add the user to the context + ctx := context.WithValue(r.Context(), KeyUser{}, acc) + r = r.WithContext(ctx) + fmt.Println(r) + // Call the next handler, which can be another middleware in the chain, or the final handler. + next.ServeHTTP(w, r) + //}) + return nil } // MiddlewareValidateTodo validates the todo in the request and calls next if ok -func (t *Todos) MiddlewareValidateTodo(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - todo := &models.Todo{} - - err := models.FromJSON(todo, r.Body) - if err != nil { - t.l.Println("[ERROR] deserializing todo", err) - - w.WriteHeader(http.StatusBadRequest) - utils.Respond(w, &GenericError{Message: err.Error()}) - return - } - - // validate the todo - errs := t.v.Validate(todo) - t.l.Println(errs.Errors()) - if len(errs) != 0 { - t.l.Println("[ERROR] validating todo", errs) - - // return the validation messages as an array - w.WriteHeader(http.StatusBadRequest) - utils.Respond(w, &ValidationError{Messages: errs.Errors()}) - return - } - - // add the todo to the context - ctx := context.WithValue(r.Context(), KeyTodo{}, todo) - r = r.WithContext(ctx) - - // Call the next handler, which can be another middleware in the chain, or the final handler. - next.ServeHTTP(w, r) - }) +func (g *GenHandler) MiddlewareValidateTodo(next http.Handler, w http.ResponseWriter, r *http.Request) error { + fmt.Println("Inside middleware todo") + + //return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + todo := &models.Todo{} + err := models.FromJSON(todo, r.Body) + if err != nil { + g.Todos.l.Println("[ERROR] deserializing todo", err) + + w.WriteHeader(http.StatusBadRequest) + utils.Respond(w, &GenericError{Message: err.Error()}) + return err + } + + // validate the todo + errs := g.Todos.v.Validate(todo) + g.Todos.l.Println(errs.Errors()) + if len(errs) != 0 { + g.Todos.l.Println("[ERROR] validating todo", errs) + + // return the validation messages as an array + w.WriteHeader(http.StatusBadRequest) + utils.Respond(w, &ValidationError{Messages: errs.Errors()}) + return err + } + + // add the todo to the context + ctx := context.WithValue(r.Context(), KeyTodo{}, todo) + r = r.WithContext(ctx) + + // Call the next handler, which can be another middleware in the chain, or the final handler. + next.ServeHTTP(w, r) + return nil + //}) } diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/post.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/post.go index 83ee853..872a82b 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/post.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/post.go @@ -1,6 +1,7 @@ package controllers import ( + "fmt" "golang_restful_api/models" "golang_restful_api/utils" "net/http" @@ -9,6 +10,7 @@ import ( // Create handles POST requests to add new users func (a *Users) Create(w http.ResponseWriter, r *http.Request) { // fetch the user from the context + fmt.Println("Inside Create") acc := r.Context().Value(KeyUser{}).(*models.User) err := a.us.CreateUser(acc) if err != nil { @@ -25,6 +27,9 @@ func (a *Users) Create(w http.ResponseWriter, r *http.Request) { func (t *Todos) Create(w http.ResponseWriter, r *http.Request) { // fetch the user from the context todo := r.Context().Value(KeyTodo{}).(*models.Todo) + fmt.Println(todo) + id := getUserID(r) + todo.UserID = id err := t.ts.AddTodo(todo) if err != nil { t.l.Println("[ERROR] Something went wrong with todo creation", err) diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/main.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/main.go index d1fc77f..dd17901 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/main.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/main.go @@ -9,7 +9,6 @@ import ( "os" "time" - "fmt" "github.com/gorilla/mux" ) @@ -41,6 +40,10 @@ func main() { // create the handler/conrollers ah := controllers.NewUsers(l, v, us) th := controllers.NewTodos(l_todo, v, ts, us) + gh := controllers.GenHandler{ + ah, + th, + } // Create new serve mux and register handlers r := mux.NewRouter() @@ -55,25 +58,12 @@ func main() { postR := apiV1.Methods("POST").Subrouter() postR.HandleFunc("/users", ah.Create) - err = r.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error { - pathTemplate, err := route.GetPathTemplate() - if err == nil { - fmt.Println("ROUTE:", pathTemplate) - } - pathRegexp, err := route.GetPathRegexp() - if err == nil { - fmt.Println("Path regexp:", pathRegexp) - } - return nil - }) - //postR.Use(ah.MiddlewareValidateUser) postR.HandleFunc("/users/{id:[0-9]+}/todos", th.Create) - //postR.Use(th.MiddlewareValidateTodo) - postR.Use(controllers.MiddlewareValidate) + postR.Use(gh.MiddlewareValidate) putR := apiV1.Methods("PUT").Subrouter() putR.HandleFunc("/users/{id:[0-9]+}", ah.Update) - putR.Use(ah.MiddlewareValidateUser) + //putR.Use(gh.MiddlewareValidateUser) deleteR := apiV1.Methods(http.MethodDelete).Subrouter() deleteR.HandleFunc("/users/{id:[0-9]+}", ah.Delete) diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/services.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/services.go index 54cf94d..38e5396 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/services.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/services.go @@ -63,15 +63,20 @@ func (s *Services) Close() error { // DestructiveReset drops all tables and rebuilds them // FOR DEVELOPMENT -func (s *Services) DestructiveReset() error { +func (s *Services) DestructiveReset() (error, error) { err := s.db.DropTableIfExists(&User{}, &Todo{}).Error if err != nil { - return err + return err, nil } - return s.AutoMigrate() + return s.AutoMigrateUser(), s.AutoMigrateTodo() } -// AutoMigrate will attempt automatically migrate all tables -func (s *Services) AutoMigrate() error { - return s.db.AutoMigrate(&User{}, &Todo{}).Error +// // AutoMigrateUser will attempt automatically migrate user table +func (s *Services) AutoMigrateUser() error { + return s.db.AutoMigrate(&User{}).Error +} + +// AutoMigrateTodo will attempt automatically migrate +func (s *Services) AutoMigrateTodo() error { + return s.db.AutoMigrate(Todo{}).AddForeignKey("user_id", "users(id)", "RESTRICT", "RESTRICT").Error } diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/todos.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/todos.go index 0808c2a..afb006e 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/todos.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/todos.go @@ -1,12 +1,14 @@ package models -import "github.com/jinzhu/gorm" +import ( + "github.com/jinzhu/gorm" +) type Todo struct { gorm.Model Title string `json:"title" validate:"required,min=5,max=20" gorm:"not null"` Description string `json:"description" validate:"required,min=10,max=100" gorm:"not null"` - UserID uint `json:"-"` + UserID uint `json:"-" gorm:"not null"` } // TodoDB interface for holding all direct database related actions @@ -48,9 +50,7 @@ type todoGorm struct { func (tg *todoGorm) GetTodos(user *User) ([]*Todo, error) { var todos []*Todo - var todo *Todo - err := tg.db.Model(&user).Related(&todo).Find(&todos).Error - // err := tg.db.Find(&todo).Error + err := tg.db.Model(&user).Related(&todos).Error return todos, err } From e951aeaa9a553b3aa74b78185cd7acbf6272fd99 Mon Sep 17 00:00:00 2001 From: Shahriyar Rzayev Date: Thu, 7 May 2020 20:12:09 +0400 Subject: [PATCH 07/30] Finalized Todo api integration with User api --- .../golang_rest_api/controllers/delete.go | 47 ++++++++++++++++ .../golang_rest_api/controllers/get.go | 53 ++++++++++++++++++- .../golang_rest_api/controllers/middleware.go | 20 ++----- .../golang_rest_api/controllers/put.go | 44 +++++++++++++++ .../golang_rest_api/controllers/todos.go | 18 +++++++ .../Golang_Gorilla/golang_rest_api/main.go | 7 ++- .../golang_rest_api/models/todos.go | 25 +++++---- 7 files changed, 188 insertions(+), 26 deletions(-) diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/delete.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/delete.go index 99fa436..7969cff 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/delete.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/delete.go @@ -31,3 +31,50 @@ func (a *Users) Delete(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNoContent) } + +// Delete handles DELETE requests and removes items from the database +func (t *Todos) Delete(w http.ResponseWriter, r *http.Request) { + id := getUserID(r) + t.l.Println("[DEBUG] get record id", id) + + acc, err := t.us.GetUserByID(id) + + switch err { + case nil: + + case models.ErrNotFound: + t.l.Println("[ERROR] fetching user", err) + + w.WriteHeader(http.StatusNotFound) + utils.Respond(w, &GenericError{Message: err.Error()}) + return + default: + t.l.Println("[ERROR] fetching user", err) + + w.WriteHeader(http.StatusInternalServerError) + utils.Respond(w, &GenericError{Message: err.Error()}) + return + } + + tid := getTodoID(r) + t.l.Println("[DEBUG] deleting record id", tid) + + err = t.ts.DeleteTodo(acc, tid) + if err == models.ErrNotFound { + t.l.Println("[ERROR] deleting record id does not exist") + + w.WriteHeader(http.StatusNotFound) + utils.Respond(w, &GenericError{Message: err.Error()}) + return + } + + if err != nil { + t.l.Println("[ERROR] deleting record", err) + + w.WriteHeader(http.StatusInternalServerError) + utils.Respond(w, &GenericError{Message: err.Error()}) + return + } + + w.WriteHeader(http.StatusNoContent) +} \ No newline at end of file diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/get.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/get.go index 6637e32..6d5e400 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/get.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/get.go @@ -106,6 +106,57 @@ func (t *Todos) ListAll(w http.ResponseWriter, r *http.Request) { err = utils.Respond(w, todos) if err != nil { // we should never be here but log the error just incase - t.l.Println("[ERROR] serializing user", err) + t.l.Println("[ERROR] serializing todos", err) } } + +// ListSingle handles GET requests +func (t *Todos) ListSingle(w http.ResponseWriter, r *http.Request) { + // Get the id from request -> URL + id := getUserID(r) + + t.l.Println("[DEBUG] get record id", id) + + acc, err := t.us.GetUserByID(id) + + switch err { + case nil: + + case models.ErrNotFound: + t.l.Println("[ERROR] fetching user", err) + + w.WriteHeader(http.StatusNotFound) + utils.Respond(w, &GenericError{Message: err.Error()}) + return + default: + t.l.Println("[ERROR] fetching user", err) + + w.WriteHeader(http.StatusInternalServerError) + utils.Respond(w, &GenericError{Message: err.Error()}) + return + } + + tid := getTodoID(r) + todo, err := t.ts.GetTodoByID(acc, tid) + switch err { + case nil: + + case models.ErrNotFound: + t.l.Println("[ERROR] fetching todo", err) + + w.WriteHeader(http.StatusNotFound) + utils.Respond(w, &GenericError{Message: err.Error()}) + return + default: + t.l.Println("[ERROR] fetching todo", err) + + w.WriteHeader(http.StatusInternalServerError) + utils.Respond(w, &GenericError{Message: err.Error()}) + return + } + err = utils.Respond(w, todo) + if err != nil { + // we should never be here but log the error just incase + t.l.Println("[ERROR] serializing todos", err) + } +} \ No newline at end of file diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/middleware.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/middleware.go index a3e3c72..f90564e 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/middleware.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/middleware.go @@ -25,7 +25,9 @@ type GenHandler struct { var uRLPathRegex = map[string]string{ "^/api/v1/users$": "users", + "^/api/v1/users/(?P[0-9]+)$": "users", "^/api/v1/users/(?P[0-9]+)/todos$": "todos", + "^/api/v1/users/(?P[0-9]+)/todos/(?P[0-9]+)$": "todos", } // CommonMiddleware for updating default content type for our router @@ -40,7 +42,6 @@ func CommonMiddleware(next http.Handler) http.Handler { func (g *GenHandler) MiddlewareValidate(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var getURLPath = r.URL.Path - fmt.Println(getURLPath) for patt, val := range uRLPathRegex { match, _ := regexp.MatchString(patt, getURLPath) if match && val == "users" { @@ -56,13 +57,7 @@ func (g *GenHandler) MiddlewareValidate(next http.Handler) http.Handler { // MiddlewareValidateUser validates the user in the request and calls next if ok func (a *GenHandler) MiddlewareValidateUser(next http.Handler, w http.ResponseWriter, r *http.Request) error { - fmt.Println("Inside validator") - // return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { acc := &models.User{} - fmt.Println("Inside middleware 2") - - fmt.Println(r.Body) - err := models.FromJSON(acc, r.Body) if err != nil { a.Users.l.Println("[ERROR] deserializing user", err) @@ -94,11 +89,8 @@ func (a *GenHandler) MiddlewareValidateUser(next http.Handler, w http.ResponseWr return nil } -// MiddlewareValidateTodo validates the todo in the request and calls next if ok +// MiddlewareValidateTodo validates the todos in the request and calls next if ok func (g *GenHandler) MiddlewareValidateTodo(next http.Handler, w http.ResponseWriter, r *http.Request) error { - fmt.Println("Inside middleware todo") - - //return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { todo := &models.Todo{} err := models.FromJSON(todo, r.Body) if err != nil { @@ -109,9 +101,8 @@ func (g *GenHandler) MiddlewareValidateTodo(next http.Handler, w http.ResponseWr return err } - // validate the todo + // validate the todos errs := g.Todos.v.Validate(todo) - g.Todos.l.Println(errs.Errors()) if len(errs) != 0 { g.Todos.l.Println("[ERROR] validating todo", errs) @@ -121,12 +112,11 @@ func (g *GenHandler) MiddlewareValidateTodo(next http.Handler, w http.ResponseWr return err } - // add the todo to the context + // add the todos to the context ctx := context.WithValue(r.Context(), KeyTodo{}, todo) r = r.WithContext(ctx) // Call the next handler, which can be another middleware in the chain, or the final handler. next.ServeHTTP(w, r) return nil - //}) } diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/put.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/put.go index 5b3d46e..4fcdd88 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/put.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/put.go @@ -29,3 +29,47 @@ func (a *Users) Update(w http.ResponseWriter, r *http.Request) { // write the no content success header w.WriteHeader(http.StatusNoContent) } + +// Update handles PUT requests to update users +func (t *Todos) Update(w http.ResponseWriter, r *http.Request) { + id := getUserID(r) + t.l.Println("[DEBUG] get record id", id) + + acc, err := t.us.GetUserByID(id) + + switch err { + case nil: + + case models.ErrNotFound: + t.l.Println("[ERROR] fetching user", err) + + w.WriteHeader(http.StatusNotFound) + utils.Respond(w, &GenericError{Message: err.Error()}) + return + default: + t.l.Println("[ERROR] fetching user", err) + + w.WriteHeader(http.StatusInternalServerError) + utils.Respond(w, &GenericError{Message: err.Error()}) + return + } + + tid := getTodoID(r) + // fetch the todos from the context + todo := r.Context().Value(KeyTodo{}).(*models.Todo) + todo.ID = tid + t.l.Println("[DEBUG] updating todo with id", todo.ID) + todo.UserID = id + err = t.ts.UpdateTodo(acc, todo, tid) + + if err == models.ErrNotFound { + t.l.Println("[ERROR] todo not found", err) + + w.WriteHeader(http.StatusNotFound) + utils.Respond(w, &GenericError{Message: "Todo not found in database"}) + return + } + + // write the no content success header + w.WriteHeader(http.StatusNoContent) +} diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/todos.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/todos.go index 188ee77..a9abf81 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/todos.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/todos.go @@ -1,8 +1,11 @@ package controllers import ( + "github.com/gorilla/mux" "golang_restful_api/models" "log" + "net/http" + "strconv" ) // KeyUser is a key used for the Todo object in the context @@ -26,3 +29,18 @@ func NewTodos(l *log.Logger, v *models.Validation, us: us, } } + +// Function for getting todos id from url +func getTodoID(r *http.Request) uint { + // parse the user id from the url + vars := mux.Vars(r) + + // convert the id into an integer and return + tid, err := strconv.Atoi(vars["tid"]) + if err != nil { + // should never happen + panic(err) + } + + return uint(tid) +} \ No newline at end of file diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/main.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/main.go index dd17901..c947ab8 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/main.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/main.go @@ -55,6 +55,8 @@ func main() { getR.HandleFunc("/users", ah.ListAll) getR.HandleFunc("/users/{id:[0-9]+}", ah.ListSingle) getR.HandleFunc("/users/{id:[0-9]+}/todos", th.ListAll) + getR.HandleFunc("/users/{id:[0-9]+}/todos/{tid:[0-9]+}", th.ListSingle) + postR := apiV1.Methods("POST").Subrouter() postR.HandleFunc("/users", ah.Create) @@ -63,10 +65,13 @@ func main() { putR := apiV1.Methods("PUT").Subrouter() putR.HandleFunc("/users/{id:[0-9]+}", ah.Update) - //putR.Use(gh.MiddlewareValidateUser) + putR.HandleFunc("/users/{id:[0-9]+}/todos/{tid:[0-9]+}", th.Update) + putR.Use(gh.MiddlewareValidate) deleteR := apiV1.Methods(http.MethodDelete).Subrouter() deleteR.HandleFunc("/users/{id:[0-9]+}", ah.Delete) + deleteR.HandleFunc("/users/{id:[0-9]+}/todos/{tid:[0-9]+}", th.Delete) + // create a new server l.Println(cfg.Port) diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/todos.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/todos.go index afb006e..6e82f8d 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/todos.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/todos.go @@ -16,12 +16,12 @@ type Todo struct { type TodoDB interface { // Methods for querying todos GetTodos(user *User) ([]*Todo, error) - GetTodoByID(id uint) (*Todo, error) + GetTodoByID(user *User, id uint) (*Todo, error) // Methods for altering the todos - UpdateTodo(td *Todo) error + UpdateTodo(user *User, todo *Todo, id uint) error AddTodo(td *Todo) error - DeleteTodo(id uint) error + DeleteTodo(user *User, id uint) error } type TodoService interface { @@ -54,16 +54,23 @@ func (tg *todoGorm) GetTodos(user *User) ([]*Todo, error) { return todos, err } -func (tg *todoGorm) GetTodoByID(id uint) (*Todo, error) { +func (tg *todoGorm) GetTodoByID(user *User, id uint) (*Todo, error) { var todo Todo - db := tg.db.Where("id = ?", id) + var todos []*Todo + db := tg.db.Model(&user).Related(&todos).Where("id = ?", id) + //db := tg.db.Where("id = ?", id) err := first(db, &todo) return &todo, err } // Update the record in todo table i.e given new todo model -func (tg *todoGorm) UpdateTodo(todo *Todo) error { - return tg.db.Save(todo).Error +func (tg *todoGorm) UpdateTodo(user *User, todo *Todo, id uint) error { + todos, err := tg.GetTodoByID(user, id) + if err != nil { + return err + } + todos = todo + return tg.db.Save(todos).Error } // Create will create provided todo and backfill data @@ -73,8 +80,8 @@ func (tg *todoGorm) AddTodo(todo *Todo) error { } // Delete the user with provided ID -func (tg *todoGorm) DeleteTodo(id uint) error { - todo, err := tg.GetTodoByID(id) +func (tg *todoGorm) DeleteTodo(user *User, id uint) error { + todo, err := tg.GetTodoByID(user, id) if err != nil { return err } From 9886ae087a2aa1aedc775b36b826d4b3678db77a Mon Sep 17 00:00:00 2001 From: Shahriyar Rzayev Date: Mon, 11 May 2020 20:42:12 +0400 Subject: [PATCH 08/30] Started to implement JWT --- .../golang_rest_api/auth/token.go | 88 +++++++++++++++++++ .../golang_rest_api/controllers/middleware.go | 16 ++++ .../golang_rest_api/models/users.go | 21 +++++ 3 files changed, 125 insertions(+) create mode 100644 Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/auth/token.go diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/auth/token.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/auth/token.go new file mode 100644 index 0000000..2261092 --- /dev/null +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/auth/token.go @@ -0,0 +1,88 @@ +package auth + +import ( +"encoding/json" +"fmt" +"log" +"net/http" +"os" +"strconv" +"strings" +"time" + +jwt "github.com/dgrijalva/jwt-go" +) + +var ApiKey = []byte("my-super-secret-secret") + +func CreateToken(userId uint32) (string, error) { + claims := jwt.MapClaims{} + claims["authorized"] = true + claims["user_id"] = userId + claims["exp"] = time.Now().Add(time.Minute * 3).Unix() + claims["iat"] = time.Now().Unix() + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + return token.SignedString(ApiKey) +} + +func TokenValid(r *http.Request) error { + tokenString := ExtractToken(r) + token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) + } + return ApiKey, nil + }) + if err != nil { + return err + } + if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { + Pretty(claims) + } + return nil +} + +func ExtractToken(r *http.Request) string { + keys := r.URL.Query() + token := keys.Get("token") + if token != "" { + return token + } + bearerToken := r.Header.Get("Authorization") + if len(strings.Split(bearerToken, " ")) == 2 { + return strings.Split(bearerToken, " ")[1] + } + return "" +} + +func ExtractTokenID(r *http.Request) (uint32, error) { + tokenString := ExtractToken(r) + token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) + } + return ApiKey, nil + }) + if err != nil { + return 0, err + } + claims, ok := token.Claims.(jwt.MapClaims) + if ok && token.Valid { + uid, err := strconv.ParseUint(fmt.Sprintf("%.0f", claims["user_id"]), 10, 32) + if err != nil { + return 0, err + } + return uint32(uid), nil + } + return 0, nil +} + +//Pretty display the claims nicely in the terminal +func Pretty(data interface{}) { + b, err := json.MarshalIndent(data, "", " ") + if err != nil { + log.Println(err) + return + } + fmt.Println(string(b)) +} \ No newline at end of file diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/middleware.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/middleware.go index f90564e..7cf66c2 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/middleware.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/middleware.go @@ -2,12 +2,15 @@ package controllers import ( "context" + "errors" "net/http" "regexp" "fmt" "golang_restful_api/models" "golang_restful_api/utils" + "golang_restful_api/auth" + ) //type genF = func(*GenHandler, http.Handler) http.Handler @@ -38,6 +41,19 @@ func CommonMiddleware(next http.Handler) http.Handler { }) } +// SetMiddlewareAuthentication ... +func SetMiddlewareAuthentication(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + err := auth.TokenValid(r) + if err != nil { + w.WriteHeader(http.StatusUnauthorized) + utils.Respond(w, &GenericError{Message: err.Error()}) + return + } + next.ServeHTTP(w, r) + }) +} + // MiddlewareValidate general middleware method for calling specific validations based on path. func (g *GenHandler) MiddlewareValidate(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/users.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/users.go index 986ab39..e20e4f0 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/users.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/users.go @@ -39,6 +39,7 @@ type UserDBExtra interface { type UserService interface { UserDB UserDBExtra + Authenticate(email, password string) (*User, error) } // NewUserService creating user service here @@ -57,6 +58,26 @@ type userService struct { pepper string } +// Authenticate Can be used to authenticate the user with the +// provided email address and password. +func (us *userService) Authenticate(email, password string) (*User, error) { + foundUser, err := us.GetUserByEmail(email) + if err != nil { + return nil, err + } + err = bcrypt.CompareHashAndPassword([]byte(foundUser.PasswordHash), + []byte(password + us.pepper)) + if err != nil { + switch err { + case bcrypt.ErrMismatchedHashAndPassword: + return nil, ErrPasswordIncorrect + default: + return nil, err + } + } + return foundUser, nil +} + var _ UserDB = &userGorm{} type userGorm struct { From 16d72e6a89096b93e4521d917dbfa0175cc4d246 Mon Sep 17 00:00:00 2001 From: Shahriyar Rzayev Date: Tue, 12 May 2020 00:51:01 +0400 Subject: [PATCH 09/30] Initial code for /login action --- .../golang_rest_api/auth/token.go | 1 - .../golang_rest_api/controllers/login.go | 44 +++++++++++++++++ .../golang_rest_api/controllers/middleware.go | 49 ++++++++++++++++--- .../Golang_Gorilla/golang_rest_api/go.mod | 1 + .../Golang_Gorilla/golang_rest_api/go.sum | 3 ++ .../Golang_Gorilla/golang_rest_api/main.go | 8 +-- .../golang_rest_api/models/errors.go | 3 ++ .../golang_rest_api/models/users.go | 5 ++ 8 files changed, 102 insertions(+), 12 deletions(-) create mode 100644 Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/login.go diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/auth/token.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/auth/token.go index 2261092..1f806e0 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/auth/token.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/auth/token.go @@ -5,7 +5,6 @@ import ( "fmt" "log" "net/http" -"os" "strconv" "strings" "time" diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/login.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/login.go new file mode 100644 index 0000000..594fee3 --- /dev/null +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/login.go @@ -0,0 +1,44 @@ +package controllers + +import ( + "fmt" + "net/http" +) + +type KeyLogin struct{} +type KeyToken struct{} + +func (us *Users) Login(w http.ResponseWriter, r *http.Request) { + fmt.Println("Success") + fmt.Println(r.Context().Value("KeyLogin")) +} + +// TODO: currently fails to compile uncomment and fix this to read Login credentials +// This function should be called only after middleware validation check +//func (us *Users) Login(w http.ResponseWriter, r *http.Request) { +// body, err := ioutil.ReadAll(r.Body) +// if err != nil { +// responses.ERROR(w, http.StatusUnprocessableEntity, err) +// return +// } +// user := models.User{} +// err = json.Unmarshal(body, &user) +// if err != nil { +// responses.ERROR(w, http.StatusUnprocessableEntity, err) +// return +// } +// +// user.Prepare() +// err = user.Validate("login") +// if err != nil { +// responses.ERROR(w, http.StatusUnprocessableEntity, err) +// return +// } +// token, err := server.SignIn(user.Email, user.Password) +// if err != nil { +// formattedError := formaterror.FormatError(err.Error()) +// responses.ERROR(w, http.StatusUnprocessableEntity, formattedError) +// return +// } +// responses.JSON(w, http.StatusOK, token) +//} \ No newline at end of file diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/middleware.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/middleware.go index 7cf66c2..e86205c 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/middleware.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/middleware.go @@ -2,11 +2,10 @@ package controllers import ( "context" - "errors" + "fmt" "net/http" "regexp" - "fmt" "golang_restful_api/models" "golang_restful_api/utils" "golang_restful_api/auth" @@ -54,6 +53,41 @@ func SetMiddlewareAuthentication(next http.Handler) http.Handler { }) } +// TODO: make it as part of General Middleware based on url path +func (g *GenHandler)MiddlewareValidateLogin(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + login := &models.Login{} + fmt.Println(r.Body) + err := models.FromJSON(login, r.Body) + if err != nil { + g.Users.l.Println("[ERROR] deserializing user login credentials", err) + + w.WriteHeader(http.StatusBadRequest) + utils.Respond(w, &GenericError{Message: err.Error()}) + return + } + + // validate the user + errs := g.Users.v.Validate(login) + g.Users.l.Println("Here: ", errs.Errors()) + if len(errs) != 0 { + g.Users.l.Println("[ERROR] validating user credentials", errs) + + // return the validation messages as an array + w.WriteHeader(http.StatusBadRequest) + utils.Respond(w, &ValidationError{Messages: errs.Errors()}) + return + } + // Add login credentials to the context. + ctx := context.WithValue(r.Context(), KeyLogin{}, login) + r = r.WithContext(ctx) + fmt.Println(r) + // Call the next handler, which can be another middleware in the chain, or the final handler. + // TODO: this will fail + next.ServeHTTP(w, r) + }) +} + // MiddlewareValidate general middleware method for calling specific validations based on path. func (g *GenHandler) MiddlewareValidate(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -72,11 +106,11 @@ func (g *GenHandler) MiddlewareValidate(next http.Handler) http.Handler { } // MiddlewareValidateUser validates the user in the request and calls next if ok -func (a *GenHandler) MiddlewareValidateUser(next http.Handler, w http.ResponseWriter, r *http.Request) error { +func (g *GenHandler) MiddlewareValidateUser(next http.Handler, w http.ResponseWriter, r *http.Request) error { acc := &models.User{} err := models.FromJSON(acc, r.Body) if err != nil { - a.Users.l.Println("[ERROR] deserializing user", err) + g.Users.l.Println("[ERROR] deserializing user", err) w.WriteHeader(http.StatusBadRequest) utils.Respond(w, &GenericError{Message: err.Error()}) @@ -84,10 +118,10 @@ func (a *GenHandler) MiddlewareValidateUser(next http.Handler, w http.ResponseWr } // validate the user - errs := a.Users.v.Validate(acc) - a.Users.l.Println("Here: ", errs.Errors()) + errs := g.Users.v.Validate(acc) + g.Users.l.Println("Here: ", errs.Errors()) if len(errs) != 0 { - a.Users.l.Println("[ERROR] validating user", errs) + g.Users.l.Println("[ERROR] validating user", errs) // return the validation messages as an array w.WriteHeader(http.StatusBadRequest) @@ -98,7 +132,6 @@ func (a *GenHandler) MiddlewareValidateUser(next http.Handler, w http.ResponseWr // add the user to the context ctx := context.WithValue(r.Context(), KeyUser{}, acc) r = r.WithContext(ctx) - fmt.Println(r) // Call the next handler, which can be another middleware in the chain, or the final handler. next.ServeHTTP(w, r) //}) diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/go.mod b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/go.mod index 5a63aba..e65f2de 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/go.mod +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/go.mod @@ -4,6 +4,7 @@ go 1.14 require ( github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/go-playground/universal-translator v0.17.0 // indirect github.com/go-playground/validator v9.31.0+incompatible github.com/gorilla/mux v1.7.4 diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/go.sum b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/go.sum index 41961a7..53c1e4d 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/go.sum +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/go.sum @@ -3,6 +3,9 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd h1:83Wprp6ROGeiHFAP8WJdI2RoxALQYgdllERc3N5N2DM= github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/dgrijalva/jwt-go v1.0.2 h1:KPldsxuKGsS2FPWsNeg9ZO18aCrGKujPoWXn2yo+KQM= +github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y= github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/main.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/main.go index c947ab8..4d80f51 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/main.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/main.go @@ -59,9 +59,11 @@ func main() { postR := apiV1.Methods("POST").Subrouter() - postR.HandleFunc("/users", ah.Create) - postR.HandleFunc("/users/{id:[0-9]+}/todos", th.Create) - postR.Use(gh.MiddlewareValidate) + //postR.HandleFunc("/users", ah.Create) + //postR.HandleFunc("/users/{id:[0-9]+}/todos", th.Create) + postR.HandleFunc("/login", ah.Login) + // postR.Use(gh.MiddlewareValidate) + postR.Use(gh.MiddlewareValidateLogin) putR := apiV1.Methods("PUT").Subrouter() putR.HandleFunc("/users/{id:[0-9]+}", ah.Update) diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/errors.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/errors.go index adbf57c..13ac204 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/errors.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/errors.go @@ -3,6 +3,9 @@ package models const ( // ErrNotFound is returned when a resource can not be found in database ErrNotFound modelError = "models: resource not found" + // ErrPasswordIncorrect is returned when an invalid password + // is used when attempting to authenticate a user. + ErrPasswordIncorrect modelError = "models: incorrect password provided" ) type modelError string diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/users.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/users.go index e20e4f0..2500f31 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/users.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/users.go @@ -16,6 +16,11 @@ type User struct { PasswordHash string `json:"-" gorm:"not null;unique_index"` } +type Login struct { + Email string `json:"email" validation:"required,email"` + Password string `json:"password" validation:"required,min=5,max=15"` +} + // UserDB interface for holding all direct database related actions type UserDB interface { // Methods for querying users From 8130a5dfad90720c1c519a17b107b8c7a8fcc053 Mon Sep 17 00:00:00 2001 From: Shahriyar Rzayev Date: Tue, 12 May 2020 20:09:17 +0400 Subject: [PATCH 10/30] Again non-working another fucking code portion --- .../golang_rest_api/auth/token.go | 2 +- .../golang_rest_api/controllers/login.go | 31 +++++- .../golang_rest_api/controllers/middleware.go | 99 +++++++++---------- .../Golang_Gorilla/golang_rest_api/main.go | 9 +- .../golang_rest_api/models/users.go | 4 +- 5 files changed, 83 insertions(+), 62 deletions(-) diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/auth/token.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/auth/token.go index 1f806e0..c91a926 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/auth/token.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/auth/token.go @@ -14,7 +14,7 @@ jwt "github.com/dgrijalva/jwt-go" var ApiKey = []byte("my-super-secret-secret") -func CreateToken(userId uint32) (string, error) { +func CreateToken(userId uint) (string, error) { claims := jwt.MapClaims{} claims["authorized"] = true claims["user_id"] = userId diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/login.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/login.go index 594fee3..b6b1e41 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/login.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/login.go @@ -1,16 +1,41 @@ package controllers import ( + "context" "fmt" + "golang_restful_api/auth" + "golang_restful_api/models" + "golang_restful_api/utils" "net/http" ) type KeyLogin struct{} type KeyToken struct{} -func (us *Users) Login(w http.ResponseWriter, r *http.Request) { - fmt.Println("Success") - fmt.Println(r.Context().Value("KeyLogin")) +func (us *Users) Login(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Println("Inside Login handler") + login := r.Context().Value(KeyLogin{}).(*models.Login) + + foundUser, err := us.us.Authenticate(login.Email, login.Password) + if err != nil { + us.l.Println("[ERROR] Something went wrong with user authentication", err) + w.WriteHeader(http.StatusBadRequest) + utils.Respond(w, &GenericError{Message: "Something went wrong with user authentication"}) + return + } + token, err := auth.CreateToken(foundUser.ID) + if err != nil { + us.l.Println("[ERROR] Something went wrong with user token creation", err) + w.WriteHeader(http.StatusUnprocessableEntity) + utils.Respond(w, &GenericError{Message: "Something went wrong with user token creation"}) + return + } + ctx := context.WithValue(r.Context(), KeyToken{}, token) + r = r.WithContext(ctx) + next.ServeHTTP(w, r) + }) + } // TODO: currently fails to compile uncomment and fix this to read Login credentials diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/middleware.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/middleware.go index e86205c..1575d0e 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/middleware.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/middleware.go @@ -6,10 +6,9 @@ import ( "net/http" "regexp" + "golang_restful_api/auth" "golang_restful_api/models" "golang_restful_api/utils" - "golang_restful_api/auth" - ) //type genF = func(*GenHandler, http.Handler) http.Handler @@ -26,10 +25,11 @@ type GenHandler struct { // } var uRLPathRegex = map[string]string{ - "^/api/v1/users$": "users", - "^/api/v1/users/(?P[0-9]+)$": "users", - "^/api/v1/users/(?P[0-9]+)/todos$": "todos", + "^/api/v1/users$": "users", + "^/api/v1/users/(?P[0-9]+)$": "users", + "^/api/v1/users/(?P[0-9]+)/todos$": "todos", "^/api/v1/users/(?P[0-9]+)/todos/(?P[0-9]+)$": "todos", + "^/api/v1/users/login$": "login", } // CommonMiddleware for updating default content type for our router @@ -53,41 +53,6 @@ func SetMiddlewareAuthentication(next http.Handler) http.Handler { }) } -// TODO: make it as part of General Middleware based on url path -func (g *GenHandler)MiddlewareValidateLogin(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - login := &models.Login{} - fmt.Println(r.Body) - err := models.FromJSON(login, r.Body) - if err != nil { - g.Users.l.Println("[ERROR] deserializing user login credentials", err) - - w.WriteHeader(http.StatusBadRequest) - utils.Respond(w, &GenericError{Message: err.Error()}) - return - } - - // validate the user - errs := g.Users.v.Validate(login) - g.Users.l.Println("Here: ", errs.Errors()) - if len(errs) != 0 { - g.Users.l.Println("[ERROR] validating user credentials", errs) - - // return the validation messages as an array - w.WriteHeader(http.StatusBadRequest) - utils.Respond(w, &ValidationError{Messages: errs.Errors()}) - return - } - // Add login credentials to the context. - ctx := context.WithValue(r.Context(), KeyLogin{}, login) - r = r.WithContext(ctx) - fmt.Println(r) - // Call the next handler, which can be another middleware in the chain, or the final handler. - // TODO: this will fail - next.ServeHTTP(w, r) - }) -} - // MiddlewareValidate general middleware method for calling specific validations based on path. func (g *GenHandler) MiddlewareValidate(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -97,16 +62,51 @@ func (g *GenHandler) MiddlewareValidate(next http.Handler) http.Handler { if match && val == "users" { g.MiddlewareValidateUser(next, w, r) break - } else { + } else if match && val == "todos" { g.MiddlewareValidateTodo(next, w, r) break + } else { + // Matching /login then + g.MiddlewareValidateLogin(next, w, r) + break } } }) } +func (g *GenHandler) MiddlewareValidateLogin(next http.Handler, w http.ResponseWriter, r *http.Request) { + login := &models.Login{} + fmt.Println(r.Body) + err := models.FromJSON(login, r.Body) + if err != nil { + g.Users.l.Println("[ERROR] deserializing user login credentials", err) + + w.WriteHeader(http.StatusBadRequest) + utils.Respond(w, &GenericError{Message: err.Error()}) + return + } + + // validate the user + errs := g.Users.v.Validate(login) + if len(errs) != 0 { + g.Users.l.Println("[ERROR] validating user login credentials", errs) + + // return the validation messages as an array + w.WriteHeader(http.StatusBadRequest) + utils.Respond(w, &ValidationError{Messages: errs.Errors()}) + return + } + // Add login credentials to the context. + ctx := context.WithValue(r.Context(), KeyLogin{}, login) + r = r.WithContext(ctx) + // Call the next handler, which can be another middleware in the chain, or the final handler. + // TODO: this will fail + next.ServeHTTP(w, r) + return +} + // MiddlewareValidateUser validates the user in the request and calls next if ok -func (g *GenHandler) MiddlewareValidateUser(next http.Handler, w http.ResponseWriter, r *http.Request) error { +func (g *GenHandler) MiddlewareValidateUser(next http.Handler, w http.ResponseWriter, r *http.Request) { acc := &models.User{} err := models.FromJSON(acc, r.Body) if err != nil { @@ -114,19 +114,18 @@ func (g *GenHandler) MiddlewareValidateUser(next http.Handler, w http.ResponseWr w.WriteHeader(http.StatusBadRequest) utils.Respond(w, &GenericError{Message: err.Error()}) - return err + return } // validate the user errs := g.Users.v.Validate(acc) - g.Users.l.Println("Here: ", errs.Errors()) if len(errs) != 0 { g.Users.l.Println("[ERROR] validating user", errs) // return the validation messages as an array w.WriteHeader(http.StatusBadRequest) utils.Respond(w, &ValidationError{Messages: errs.Errors()}) - return err + return } // add the user to the context @@ -134,12 +133,11 @@ func (g *GenHandler) MiddlewareValidateUser(next http.Handler, w http.ResponseWr r = r.WithContext(ctx) // Call the next handler, which can be another middleware in the chain, or the final handler. next.ServeHTTP(w, r) - //}) - return nil + } // MiddlewareValidateTodo validates the todos in the request and calls next if ok -func (g *GenHandler) MiddlewareValidateTodo(next http.Handler, w http.ResponseWriter, r *http.Request) error { +func (g *GenHandler) MiddlewareValidateTodo(next http.Handler, w http.ResponseWriter, r *http.Request) { todo := &models.Todo{} err := models.FromJSON(todo, r.Body) if err != nil { @@ -147,7 +145,7 @@ func (g *GenHandler) MiddlewareValidateTodo(next http.Handler, w http.ResponseWr w.WriteHeader(http.StatusBadRequest) utils.Respond(w, &GenericError{Message: err.Error()}) - return err + return } // validate the todos @@ -158,7 +156,7 @@ func (g *GenHandler) MiddlewareValidateTodo(next http.Handler, w http.ResponseWr // return the validation messages as an array w.WriteHeader(http.StatusBadRequest) utils.Respond(w, &ValidationError{Messages: errs.Errors()}) - return err + return } // add the todos to the context @@ -167,5 +165,4 @@ func (g *GenHandler) MiddlewareValidateTodo(next http.Handler, w http.ResponseWr // Call the next handler, which can be another middleware in the chain, or the final handler. next.ServeHTTP(w, r) - return nil } diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/main.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/main.go index 4d80f51..ce78483 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/main.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/main.go @@ -59,11 +59,10 @@ func main() { postR := apiV1.Methods("POST").Subrouter() - //postR.HandleFunc("/users", ah.Create) - //postR.HandleFunc("/users/{id:[0-9]+}/todos", th.Create) - postR.HandleFunc("/login", ah.Login) - // postR.Use(gh.MiddlewareValidate) - postR.Use(gh.MiddlewareValidateLogin) + postR.HandleFunc("/users", ah.Create) + postR.HandleFunc("/users/{id:[0-9]+}/todos", th.Create) + postR.Handle("/users/login", ah.Login) + postR.Use(gh.MiddlewareValidate) putR := apiV1.Methods("PUT").Subrouter() putR.HandleFunc("/users/{id:[0-9]+}", ah.Update) diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/users.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/users.go index 2500f31..857889c 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/users.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/users.go @@ -17,7 +17,7 @@ type User struct { } type Login struct { - Email string `json:"email" validation:"required,email"` + Email string `json:"email" validation:"required,email"` Password string `json:"password" validation:"required,min=5,max=15"` } @@ -71,7 +71,7 @@ func (us *userService) Authenticate(email, password string) (*User, error) { return nil, err } err = bcrypt.CompareHashAndPassword([]byte(foundUser.PasswordHash), - []byte(password + us.pepper)) + []byte(password+us.pepper)) if err != nil { switch err { case bcrypt.ErrMismatchedHashAndPassword: From 4c854e4be048e3e4c380914886c54854f3d90094 Mon Sep 17 00:00:00 2001 From: Shahriyar Rzayev Date: Tue, 12 May 2020 22:22:26 +0400 Subject: [PATCH 11/30] Implemented the Login --- .../golang_rest_api/controllers/login.go | 76 +++++-------------- .../golang_rest_api/controllers/post.go | 1 + .../Golang_Gorilla/golang_rest_api/main.go | 2 +- 3 files changed, 23 insertions(+), 56 deletions(-) diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/login.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/login.go index b6b1e41..e863204 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/login.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/login.go @@ -1,7 +1,6 @@ package controllers import ( - "context" "fmt" "golang_restful_api/auth" "golang_restful_api/models" @@ -10,60 +9,27 @@ import ( ) type KeyLogin struct{} -type KeyToken struct{} -func (us *Users) Login(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - fmt.Println("Inside Login handler") - login := r.Context().Value(KeyLogin{}).(*models.Login) - - foundUser, err := us.us.Authenticate(login.Email, login.Password) - if err != nil { - us.l.Println("[ERROR] Something went wrong with user authentication", err) - w.WriteHeader(http.StatusBadRequest) - utils.Respond(w, &GenericError{Message: "Something went wrong with user authentication"}) - return - } - token, err := auth.CreateToken(foundUser.ID) - if err != nil { - us.l.Println("[ERROR] Something went wrong with user token creation", err) - w.WriteHeader(http.StatusUnprocessableEntity) - utils.Respond(w, &GenericError{Message: "Something went wrong with user token creation"}) - return - } - ctx := context.WithValue(r.Context(), KeyToken{}, token) - r = r.WithContext(ctx) - next.ServeHTTP(w, r) - }) +func (us *Users) Login(w http.ResponseWriter, r *http.Request) { + fmt.Println("Inside Login handler") + login := r.Context().Value(KeyLogin{}).(*models.Login) + foundUser, err := us.us.Authenticate(login.Email, login.Password) + if err != nil { + us.l.Println("[ERROR] Something went wrong with user authentication", err) + w.WriteHeader(http.StatusBadRequest) + utils.Respond(w, &GenericError{Message: "Something went wrong with user authentication"}) + return + } + token, err := auth.CreateToken(foundUser.ID) + if err != nil { + us.l.Println("[ERROR] Something went wrong with user token creation", err) + w.WriteHeader(http.StatusUnprocessableEntity) + utils.Respond(w, &GenericError{Message: "Something went wrong with user token creation"}) + return + } + err = utils.Respond(w, token) + if err != nil { + us.l.Println("[ERROR] serializing token", err) + } } - -// TODO: currently fails to compile uncomment and fix this to read Login credentials -// This function should be called only after middleware validation check -//func (us *Users) Login(w http.ResponseWriter, r *http.Request) { -// body, err := ioutil.ReadAll(r.Body) -// if err != nil { -// responses.ERROR(w, http.StatusUnprocessableEntity, err) -// return -// } -// user := models.User{} -// err = json.Unmarshal(body, &user) -// if err != nil { -// responses.ERROR(w, http.StatusUnprocessableEntity, err) -// return -// } -// -// user.Prepare() -// err = user.Validate("login") -// if err != nil { -// responses.ERROR(w, http.StatusUnprocessableEntity, err) -// return -// } -// token, err := server.SignIn(user.Email, user.Password) -// if err != nil { -// formattedError := formaterror.FormatError(err.Error()) -// responses.ERROR(w, http.StatusUnprocessableEntity, formattedError) -// return -// } -// responses.JSON(w, http.StatusOK, token) -//} \ No newline at end of file diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/post.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/post.go index 872a82b..b3a5fd6 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/post.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/post.go @@ -39,4 +39,5 @@ func (t *Todos) Create(w http.ResponseWriter, r *http.Request) { } w.WriteHeader(http.StatusCreated) t.l.Printf("[DEBUG] Inserting todo: %#v\n", todo) + // TODO: return created data } diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/main.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/main.go index ce78483..0e30b98 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/main.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/main.go @@ -61,7 +61,7 @@ func main() { postR := apiV1.Methods("POST").Subrouter() postR.HandleFunc("/users", ah.Create) postR.HandleFunc("/users/{id:[0-9]+}/todos", th.Create) - postR.Handle("/users/login", ah.Login) + postR.HandleFunc("/users/login", ah.Login) postR.Use(gh.MiddlewareValidate) putR := apiV1.Methods("PUT").Subrouter() From 151cd3db3da8db510b27f997f43655107498feb4 Mon Sep 17 00:00:00 2001 From: Shahriyar Rzayev Date: Wed, 13 May 2020 22:17:43 +0400 Subject: [PATCH 12/30] Finalized needed features --- .../golang_rest_api/auth/token.go | 35 +++++++++++++------ .../golang_rest_api/controllers/delete.go | 14 +++++--- .../golang_rest_api/controllers/get.go | 29 ++++++++++----- .../golang_rest_api/controllers/login.go | 21 ++++++++--- .../golang_rest_api/controllers/middleware.go | 12 ++++--- .../golang_rest_api/controllers/post.go | 15 +++++--- .../golang_rest_api/controllers/put.go | 15 +++++--- .../Golang_Gorilla/golang_rest_api/main.go | 11 +++--- 8 files changed, 105 insertions(+), 47 deletions(-) diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/auth/token.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/auth/token.go index c91a926..5fe3d63 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/auth/token.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/auth/token.go @@ -12,33 +12,46 @@ import ( jwt "github.com/dgrijalva/jwt-go" ) -var ApiKey = []byte("my-super-secret-secret") +var AccessSecret = []byte("my-super-secret-access-secret") +var RefreshSecret = []byte("my-super-secret-refresh-secret") -func CreateToken(userId uint) (string, error) { +func CreateAccessToken(userId uint) (string, error) { claims := jwt.MapClaims{} claims["authorized"] = true claims["user_id"] = userId claims["exp"] = time.Now().Add(time.Minute * 3).Unix() claims["iat"] = time.Now().Unix() + claims["type"] = "AccessToken" token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) - return token.SignedString(ApiKey) + return token.SignedString(AccessSecret) } -func TokenValid(r *http.Request) error { +func CreateRefreshToken(userId uint) (string, error) { + claims := jwt.MapClaims{} + claims["authorized"] = true + claims["user_id"] = userId + claims["exp"] = time.Now().Add(time.Hour * 3).Unix() + claims["iat"] = time.Now().Unix() + claims["type"] = "RefreshToken" + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + return token.SignedString(RefreshSecret) +} + +func TokenValid(r *http.Request) (*jwt.Token, error) { tokenString := ExtractToken(r) token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) } - return ApiKey, nil + return AccessSecret, nil }) if err != nil { - return err + return nil, err } if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { Pretty(claims) } - return nil + return token, nil } func ExtractToken(r *http.Request) string { @@ -54,24 +67,24 @@ func ExtractToken(r *http.Request) string { return "" } -func ExtractTokenID(r *http.Request) (uint32, error) { +func ExtractTokenID(r *http.Request) (uint, error) { tokenString := ExtractToken(r) token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) } - return ApiKey, nil + return AccessSecret, nil }) if err != nil { return 0, err } claims, ok := token.Claims.(jwt.MapClaims) if ok && token.Valid { - uid, err := strconv.ParseUint(fmt.Sprintf("%.0f", claims["user_id"]), 10, 32) + uid, err := strconv.ParseUint(fmt.Sprintf("%.0f", claims["user_id"]), 10, 64) if err != nil { return 0, err } - return uint32(uid), nil + return uint(uid), nil } return 0, nil } diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/delete.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/delete.go index 7969cff..b5d2715 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/delete.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/delete.go @@ -1,6 +1,7 @@ package controllers import ( + "golang_restful_api/auth" "golang_restful_api/models" "golang_restful_api/utils" "net/http" @@ -28,16 +29,21 @@ func (a *Users) Delete(w http.ResponseWriter, r *http.Request) { utils.Respond(w, &GenericError{Message: err.Error()}) return } - w.WriteHeader(http.StatusNoContent) } // Delete handles DELETE requests and removes items from the database func (t *Todos) Delete(w http.ResponseWriter, r *http.Request) { - id := getUserID(r) - t.l.Println("[DEBUG] get record id", id) + userID, err := auth.ExtractTokenID(r) + if err != nil { + t.l.Println("[ERROR] Something went wrong with token parsing", err) + w.WriteHeader(http.StatusUnprocessableEntity) + utils.Respond(w, &GenericError{Message: "Something went wrong with token parsing"}) + return + } + t.l.Println("[DEBUG] get record id", userID) - acc, err := t.us.GetUserByID(id) + acc, err := t.us.GetUserByID(userID) switch err { case nil: diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/get.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/get.go index 6d5e400..81521a1 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/get.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/get.go @@ -1,6 +1,7 @@ package controllers import ( + "golang_restful_api/auth" "golang_restful_api/models" "golang_restful_api/utils" "net/http" @@ -9,7 +10,6 @@ import ( // ListAll handles GET requests and returns all current users func (a *Users) ListAll(w http.ResponseWriter, r *http.Request) { a.l.Println("[DEBUG] get all records") - accs, err := a.us.GetUsers() switch err { case nil: @@ -71,12 +71,18 @@ func (a *Users) ListSingle(w http.ResponseWriter, r *http.Request) { // ListAll handles GET requests and returns all current todos for a given user func (t *Todos) ListAll(w http.ResponseWriter, r *http.Request) { t.l.Println("[DEBUG] get all records") - // Get the id from request -> URL - id := getUserID(r) - t.l.Println("[DEBUG] get record id", id) + userID, err := auth.ExtractTokenID(r) + if err != nil { + t.l.Println("[ERROR] Something went wrong with token parsing", err) + w.WriteHeader(http.StatusUnprocessableEntity) + utils.Respond(w, &GenericError{Message: "Something went wrong with token parsing"}) + return + } + + t.l.Println("[DEBUG] get record id", userID) - acc, err := t.us.GetUserByID(id) + acc, err := t.us.GetUserByID(userID) switch err { case nil: @@ -112,12 +118,17 @@ func (t *Todos) ListAll(w http.ResponseWriter, r *http.Request) { // ListSingle handles GET requests func (t *Todos) ListSingle(w http.ResponseWriter, r *http.Request) { - // Get the id from request -> URL - id := getUserID(r) + userID, err := auth.ExtractTokenID(r) + if err != nil { + t.l.Println("[ERROR] Something went wrong with token parsing", err) + w.WriteHeader(http.StatusUnprocessableEntity) + utils.Respond(w, &GenericError{Message: "Something went wrong with token parsing"}) + return + } - t.l.Println("[DEBUG] get record id", id) + t.l.Println("[DEBUG] get record id", userID) - acc, err := t.us.GetUserByID(id) + acc, err := t.us.GetUserByID(userID) switch err { case nil: diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/login.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/login.go index e863204..dd58b55 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/login.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/login.go @@ -20,16 +20,27 @@ func (us *Users) Login(w http.ResponseWriter, r *http.Request) { utils.Respond(w, &GenericError{Message: "Something went wrong with user authentication"}) return } - token, err := auth.CreateToken(foundUser.ID) + accessToken, err := auth.CreateAccessToken(foundUser.ID) if err != nil { - us.l.Println("[ERROR] Something went wrong with user token creation", err) + us.l.Println("[ERROR] Something went wrong with user Access token creation", err) w.WriteHeader(http.StatusUnprocessableEntity) - utils.Respond(w, &GenericError{Message: "Something went wrong with user token creation"}) + utils.Respond(w, &GenericError{Message: "Something went wrong with user Access token creation"}) return } - err = utils.Respond(w, token) + refreshToken, err := auth.CreateRefreshToken(foundUser.ID) if err != nil { - us.l.Println("[ERROR] serializing token", err) + us.l.Println("[ERROR] Something went wrong with user Refresh token creation", err) + w.WriteHeader(http.StatusUnprocessableEntity) + utils.Respond(w, &GenericError{Message: "Something went wrong with user Refresh token creation"}) + return + } + tokens := map[string]string { + "AccessToken": accessToken, + "RefreshToken": refreshToken, + } + err = utils.Respond(w, tokens) + if err != nil { + us.l.Println("[ERROR] serializing tokens", err) } } diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/middleware.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/middleware.go index 1575d0e..6cfb2ad 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/middleware.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/middleware.go @@ -27,8 +27,8 @@ type GenHandler struct { var uRLPathRegex = map[string]string{ "^/api/v1/users$": "users", "^/api/v1/users/(?P[0-9]+)$": "users", - "^/api/v1/users/(?P[0-9]+)/todos$": "todos", - "^/api/v1/users/(?P[0-9]+)/todos/(?P[0-9]+)$": "todos", + "^/api/v1/users/todos$": "todos", + "^/api/v1/users/todos/(?P[0-9]+)$": "todos", "^/api/v1/users/login$": "login", } @@ -41,9 +41,9 @@ func CommonMiddleware(next http.Handler) http.Handler { } // SetMiddlewareAuthentication ... -func SetMiddlewareAuthentication(next http.Handler) http.Handler { +func (g *GenHandler) SetMiddlewareAuthentication(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - err := auth.TokenValid(r) + _, err := auth.TokenValid(r) if err != nil { w.WriteHeader(http.StatusUnauthorized) utils.Respond(w, &GenericError{Message: err.Error()}) @@ -63,9 +63,11 @@ func (g *GenHandler) MiddlewareValidate(next http.Handler) http.Handler { g.MiddlewareValidateUser(next, w, r) break } else if match && val == "todos" { + // TODO: seems to be the first check does not work. + g.SetMiddlewareAuthentication(next) g.MiddlewareValidateTodo(next, w, r) break - } else { + } else if match && val == "login" { // Matching /login then g.MiddlewareValidateLogin(next, w, r) break diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/post.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/post.go index b3a5fd6..936c4ca 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/post.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/post.go @@ -2,6 +2,7 @@ package controllers import ( "fmt" + "golang_restful_api/auth" "golang_restful_api/models" "golang_restful_api/utils" "net/http" @@ -27,10 +28,16 @@ func (a *Users) Create(w http.ResponseWriter, r *http.Request) { func (t *Todos) Create(w http.ResponseWriter, r *http.Request) { // fetch the user from the context todo := r.Context().Value(KeyTodo{}).(*models.Todo) - fmt.Println(todo) - id := getUserID(r) - todo.UserID = id - err := t.ts.AddTodo(todo) + userID, err := auth.ExtractTokenID(r) + if err != nil { + t.l.Println("[ERROR] Something went wrong with token parsing", err) + w.WriteHeader(http.StatusUnprocessableEntity) + utils.Respond(w, &GenericError{Message: "Something went wrong with token parsing"}) + return + } + //id := getUserID(r) + todo.UserID = userID + err = t.ts.AddTodo(todo) if err != nil { t.l.Println("[ERROR] Something went wrong with todo creation", err) w.WriteHeader(http.StatusBadRequest) diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/put.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/put.go index 4fcdd88..dc99dfc 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/put.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/put.go @@ -1,6 +1,7 @@ package controllers import ( + "golang_restful_api/auth" "golang_restful_api/models" "golang_restful_api/utils" "net/http" @@ -32,10 +33,16 @@ func (a *Users) Update(w http.ResponseWriter, r *http.Request) { // Update handles PUT requests to update users func (t *Todos) Update(w http.ResponseWriter, r *http.Request) { - id := getUserID(r) - t.l.Println("[DEBUG] get record id", id) + userID, err := auth.ExtractTokenID(r) + if err != nil { + t.l.Println("[ERROR] Something went wrong with token parsing", err) + w.WriteHeader(http.StatusUnprocessableEntity) + utils.Respond(w, &GenericError{Message: "Something went wrong with token parsing"}) + return + } + t.l.Println("[DEBUG] get record id", userID) - acc, err := t.us.GetUserByID(id) + acc, err := t.us.GetUserByID(userID) switch err { case nil: @@ -59,7 +66,7 @@ func (t *Todos) Update(w http.ResponseWriter, r *http.Request) { todo := r.Context().Value(KeyTodo{}).(*models.Todo) todo.ID = tid t.l.Println("[DEBUG] updating todo with id", todo.ID) - todo.UserID = id + todo.UserID = userID err = t.ts.UpdateTodo(acc, todo, tid) if err == models.ErrNotFound { diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/main.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/main.go index 0e30b98..01298aa 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/main.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/main.go @@ -54,24 +54,25 @@ func main() { getR := apiV1.Methods("GET").Subrouter() getR.HandleFunc("/users", ah.ListAll) getR.HandleFunc("/users/{id:[0-9]+}", ah.ListSingle) - getR.HandleFunc("/users/{id:[0-9]+}/todos", th.ListAll) - getR.HandleFunc("/users/{id:[0-9]+}/todos/{tid:[0-9]+}", th.ListSingle) + getR.HandleFunc("/users/todos", th.ListAll) + getR.HandleFunc("/users/todos/{tid:[0-9]+}", th.ListSingle) postR := apiV1.Methods("POST").Subrouter() postR.HandleFunc("/users", ah.Create) - postR.HandleFunc("/users/{id:[0-9]+}/todos", th.Create) + postR.HandleFunc("/users/todos", th.Create) postR.HandleFunc("/users/login", ah.Login) postR.Use(gh.MiddlewareValidate) putR := apiV1.Methods("PUT").Subrouter() putR.HandleFunc("/users/{id:[0-9]+}", ah.Update) - putR.HandleFunc("/users/{id:[0-9]+}/todos/{tid:[0-9]+}", th.Update) + putR.HandleFunc("/users/todos/{tid:[0-9]+}", th.Update) putR.Use(gh.MiddlewareValidate) deleteR := apiV1.Methods(http.MethodDelete).Subrouter() deleteR.HandleFunc("/users/{id:[0-9]+}", ah.Delete) - deleteR.HandleFunc("/users/{id:[0-9]+}/todos/{tid:[0-9]+}", th.Delete) + deleteR.HandleFunc("/users/todos/{tid:[0-9]+}", th.Delete) + deleteR.Use(gh.SetMiddlewareAuthentication) // create a new server From 6d8987c9a54d7518a60af9d43d9cbffde3b53db1 Mon Sep 17 00:00:00 2001 From: Shahriyar Rzayev Date: Sun, 17 May 2020 12:57:06 +0400 Subject: [PATCH 13/30] Code refactoring making seperate function for id token extraction --- .../golang_rest_api/controllers/delete.go | 29 +-------- .../golang_rest_api/controllers/get.go | 62 +------------------ .../golang_rest_api/controllers/post.go | 15 ++--- .../golang_rest_api/controllers/put.go | 34 +--------- .../golang_rest_api/controllers/todos.go | 35 +++++++++++ 5 files changed, 45 insertions(+), 130 deletions(-) diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/delete.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/delete.go index b5d2715..382d7d0 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/delete.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/delete.go @@ -1,7 +1,6 @@ package controllers import ( - "golang_restful_api/auth" "golang_restful_api/models" "golang_restful_api/utils" "net/http" @@ -34,33 +33,7 @@ func (a *Users) Delete(w http.ResponseWriter, r *http.Request) { // Delete handles DELETE requests and removes items from the database func (t *Todos) Delete(w http.ResponseWriter, r *http.Request) { - userID, err := auth.ExtractTokenID(r) - if err != nil { - t.l.Println("[ERROR] Something went wrong with token parsing", err) - w.WriteHeader(http.StatusUnprocessableEntity) - utils.Respond(w, &GenericError{Message: "Something went wrong with token parsing"}) - return - } - t.l.Println("[DEBUG] get record id", userID) - - acc, err := t.us.GetUserByID(userID) - - switch err { - case nil: - - case models.ErrNotFound: - t.l.Println("[ERROR] fetching user", err) - - w.WriteHeader(http.StatusNotFound) - utils.Respond(w, &GenericError{Message: err.Error()}) - return - default: - t.l.Println("[ERROR] fetching user", err) - - w.WriteHeader(http.StatusInternalServerError) - utils.Respond(w, &GenericError{Message: err.Error()}) - return - } + acc, err := t.getTokenAndUser(w, r) tid := getTodoID(r) t.l.Println("[DEBUG] deleting record id", tid) diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/get.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/get.go index 81521a1..4dc4420 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/get.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/get.go @@ -1,7 +1,6 @@ package controllers import ( - "golang_restful_api/auth" "golang_restful_api/models" "golang_restful_api/utils" "net/http" @@ -71,36 +70,7 @@ func (a *Users) ListSingle(w http.ResponseWriter, r *http.Request) { // ListAll handles GET requests and returns all current todos for a given user func (t *Todos) ListAll(w http.ResponseWriter, r *http.Request) { t.l.Println("[DEBUG] get all records") - - userID, err := auth.ExtractTokenID(r) - if err != nil { - t.l.Println("[ERROR] Something went wrong with token parsing", err) - w.WriteHeader(http.StatusUnprocessableEntity) - utils.Respond(w, &GenericError{Message: "Something went wrong with token parsing"}) - return - } - - t.l.Println("[DEBUG] get record id", userID) - - acc, err := t.us.GetUserByID(userID) - - switch err { - case nil: - - case models.ErrNotFound: - t.l.Println("[ERROR] fetching user", err) - - w.WriteHeader(http.StatusNotFound) - utils.Respond(w, &GenericError{Message: err.Error()}) - return - default: - t.l.Println("[ERROR] fetching user", err) - - w.WriteHeader(http.StatusInternalServerError) - utils.Respond(w, &GenericError{Message: err.Error()}) - return - } - + acc, err := t.getTokenAndUser(w, r) todos, err := t.ts.GetTodos(acc) if err != nil { // TODO: do better error checking @@ -118,35 +88,7 @@ func (t *Todos) ListAll(w http.ResponseWriter, r *http.Request) { // ListSingle handles GET requests func (t *Todos) ListSingle(w http.ResponseWriter, r *http.Request) { - userID, err := auth.ExtractTokenID(r) - if err != nil { - t.l.Println("[ERROR] Something went wrong with token parsing", err) - w.WriteHeader(http.StatusUnprocessableEntity) - utils.Respond(w, &GenericError{Message: "Something went wrong with token parsing"}) - return - } - - t.l.Println("[DEBUG] get record id", userID) - - acc, err := t.us.GetUserByID(userID) - - switch err { - case nil: - - case models.ErrNotFound: - t.l.Println("[ERROR] fetching user", err) - - w.WriteHeader(http.StatusNotFound) - utils.Respond(w, &GenericError{Message: err.Error()}) - return - default: - t.l.Println("[ERROR] fetching user", err) - - w.WriteHeader(http.StatusInternalServerError) - utils.Respond(w, &GenericError{Message: err.Error()}) - return - } - + acc, err := t.getTokenAndUser(w, r) tid := getTodoID(r) todo, err := t.ts.GetTodoByID(acc, tid) switch err { diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/post.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/post.go index 936c4ca..a6ebb55 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/post.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/post.go @@ -2,7 +2,6 @@ package controllers import ( "fmt" - "golang_restful_api/auth" "golang_restful_api/models" "golang_restful_api/utils" "net/http" @@ -24,19 +23,13 @@ func (a *Users) Create(w http.ResponseWriter, r *http.Request) { a.l.Printf("[DEBUG] Inserting user: %#v\n", acc) } -// Create handles POST requests to add new users +// Create handles POST requests to add new todos func (t *Todos) Create(w http.ResponseWriter, r *http.Request) { // fetch the user from the context todo := r.Context().Value(KeyTodo{}).(*models.Todo) - userID, err := auth.ExtractTokenID(r) - if err != nil { - t.l.Println("[ERROR] Something went wrong with token parsing", err) - w.WriteHeader(http.StatusUnprocessableEntity) - utils.Respond(w, &GenericError{Message: "Something went wrong with token parsing"}) - return - } - //id := getUserID(r) - todo.UserID = userID + // Get the user_id from JWT token and check if that user exists in database + acc, err := t.getTokenAndUser(w, r) + todo.UserID = acc.ID err = t.ts.AddTodo(todo) if err != nil { t.l.Println("[ERROR] Something went wrong with todo creation", err) diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/put.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/put.go index dc99dfc..383305c 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/put.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/put.go @@ -1,7 +1,6 @@ package controllers import ( - "golang_restful_api/auth" "golang_restful_api/models" "golang_restful_api/utils" "net/http" @@ -31,42 +30,15 @@ func (a *Users) Update(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNoContent) } -// Update handles PUT requests to update users +// Update handles PUT requests to update todos func (t *Todos) Update(w http.ResponseWriter, r *http.Request) { - userID, err := auth.ExtractTokenID(r) - if err != nil { - t.l.Println("[ERROR] Something went wrong with token parsing", err) - w.WriteHeader(http.StatusUnprocessableEntity) - utils.Respond(w, &GenericError{Message: "Something went wrong with token parsing"}) - return - } - t.l.Println("[DEBUG] get record id", userID) - - acc, err := t.us.GetUserByID(userID) - - switch err { - case nil: - - case models.ErrNotFound: - t.l.Println("[ERROR] fetching user", err) - - w.WriteHeader(http.StatusNotFound) - utils.Respond(w, &GenericError{Message: err.Error()}) - return - default: - t.l.Println("[ERROR] fetching user", err) - - w.WriteHeader(http.StatusInternalServerError) - utils.Respond(w, &GenericError{Message: err.Error()}) - return - } - + acc, err := t.getTokenAndUser(w, r) tid := getTodoID(r) // fetch the todos from the context todo := r.Context().Value(KeyTodo{}).(*models.Todo) todo.ID = tid t.l.Println("[DEBUG] updating todo with id", todo.ID) - todo.UserID = userID + todo.UserID = acc.ID err = t.ts.UpdateTodo(acc, todo, tid) if err == models.ErrNotFound { diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/todos.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/todos.go index a9abf81..5d1d05f 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/todos.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/todos.go @@ -2,7 +2,9 @@ package controllers import ( "github.com/gorilla/mux" + "golang_restful_api/auth" "golang_restful_api/models" + "golang_restful_api/utils" "log" "net/http" "strconv" @@ -43,4 +45,37 @@ func getTodoID(r *http.Request) uint { } return uint(tid) +} + +// Function to get the user_id from JWT token and search it in database +func (t *Todos) getTokenAndUser(w http.ResponseWriter, r *http.Request) (*models.User, error) { + userID, err := auth.ExtractTokenID(r) + if err != nil { + t.l.Println("[ERROR] Something went wrong with token parsing", err) + w.WriteHeader(http.StatusUnprocessableEntity) + utils.Respond(w, &GenericError{Message: "Something went wrong with token parsing"}) + return nil, err + } + t.l.Println("[DEBUG] get record id", userID) + + acc, err := t.us.GetUserByID(userID) + + switch err { + case nil: + + case models.ErrNotFound: + t.l.Println("[ERROR] fetching user", err) + + w.WriteHeader(http.StatusNotFound) + utils.Respond(w, &GenericError{Message: err.Error()}) + return nil, err + default: + t.l.Println("[ERROR] fetching user", err) + + w.WriteHeader(http.StatusInternalServerError) + utils.Respond(w, &GenericError{Message: err.Error()}) + return nil, err + } + + return acc, nil } \ No newline at end of file From 987eaa14cf2591e481f41682680bba20553790fb Mon Sep 17 00:00:00 2001 From: Shahriyar Rzayev Date: Sun, 17 May 2020 15:27:18 +0400 Subject: [PATCH 14/30] Added new test config file and new function --- .../golang_rest_api/.configtest | 12 +++++++++++ .../Golang_Gorilla/golang_rest_api/config.go | 20 +++++++++++++++++-- .../golang_rest_api/docker-compose.test.yaml | 12 +++++++++++ .../tests/modeltests/model_test.go | 20 +++++++++++++++++++ .../tests/modeltests/user_model_test.go | 7 +++++++ 5 files changed, 69 insertions(+), 2 deletions(-) create mode 100644 Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/.configtest create mode 100644 Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/docker-compose.test.yaml create mode 100644 Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/model_test.go create mode 100644 Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/user_model_test.go diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/.configtest b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/.configtest new file mode 100644 index 0000000..3c2dbba --- /dev/null +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/.configtest @@ -0,0 +1,12 @@ +{ + "port": ":8080", + "env": "test", + "pepper": "secret-random-test-string", + "database": { + "host": "localhost", + "port": 5433, + "user": "restapitest", + "password": "12345", + "dbname": "restapi_test" + } +} diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/config.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/config.go index 2d35327..2a2a4c9 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/config.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/config.go @@ -70,8 +70,8 @@ func DefaultConfig() Config { } } -// LoadConfig openning and loading the config file. -// if there is no .config file then use DefaultConfig +// LoadConfig opening and loading the config file. +// if there is no .config file then use DefaultConfig - if the --prod flag not provided func LoadConfig(configreq bool) Config { f, err := os.Open(".config") if err != nil { @@ -90,3 +90,19 @@ func LoadConfig(configreq bool) Config { fmt.Println("Successfully loaded .config") return c } + +func LoadTestConfig() Config { + f, err := os.Open(".configtest") + if err != nil { + fmt.Println("Error reading test config", err) + panic(err) + } + var c Config + dec := json.NewDecoder(f) + err = dec.Decode(&c) + if err != nil { + panic(err) + } + fmt.Println("Successfully loaded .configtest") + return c +} diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/docker-compose.test.yaml b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/docker-compose.test.yaml new file mode 100644 index 0000000..6c85a19 --- /dev/null +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/docker-compose.test.yaml @@ -0,0 +1,12 @@ +version: '3.7' +services: + postgres_db_test: + image: postgres + container_name: live_stream_pg_test + restart: on-failure + ports: + - "5433:5433" + environment: + - POSTGRES_USER=restapitest + - POSTGRES_PASSWORD=12345 + - POSTGRES_DB=restapi_test diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/model_test.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/model_test.go new file mode 100644 index 0000000..d8f5794 --- /dev/null +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/model_test.go @@ -0,0 +1,20 @@ +package modeltests + +import ( + "fmt" + "os" + "testing" + "golang_restful_api/main" + "golang_restful_api/models" +) + +// TODO: figure out the way to initialize necessary things at very beginning of the test +func TestMain(m *testing.M) { + cfg := main.LoadTestConfig() + dbCfg := cfg.Database + fmt.Println(dbCfg) + os.Exit(m.Run()) +} + + + diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/user_model_test.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/user_model_test.go new file mode 100644 index 0000000..ff4f1d1 --- /dev/null +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/user_model_test.go @@ -0,0 +1,7 @@ +package modeltests + +import "testing" + +func TestFindAllUsers(t *testing.T) { + +} \ No newline at end of file From 92ef88f7beab8dafe7db38db6d1c7394dc87b891 Mon Sep 17 00:00:00 2001 From: Shahriyar Rzayev Date: Mon, 18 May 2020 01:10:20 +0400 Subject: [PATCH 15/30] Started to implement first Unit test --- .../golang_rest_api/docker-compose.test.yaml | 2 +- .../Golang_Gorilla/golang_rest_api/main.go | 3 +- .../golang_rest_api/models/services.go | 7 ++ .../{ => tests/modeltests}/.configtest | 2 +- .../tests/modeltests/model_test.go | 67 +++++++++++++++++-- .../tests/modeltests/user_model_test.go | 9 ++- .../golang_rest_api/{ => utils}/config.go | 3 +- 7 files changed, 81 insertions(+), 12 deletions(-) rename Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/{ => tests/modeltests}/.configtest (92%) rename Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/{ => utils}/config.go (97%) diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/docker-compose.test.yaml b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/docker-compose.test.yaml index 6c85a19..7c4a88b 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/docker-compose.test.yaml +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/docker-compose.test.yaml @@ -5,7 +5,7 @@ services: container_name: live_stream_pg_test restart: on-failure ports: - - "5433:5433" + - "5432:5432" environment: - POSTGRES_USER=restapitest - POSTGRES_PASSWORD=12345 diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/main.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/main.go index 01298aa..6750ebb 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/main.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/main.go @@ -4,6 +4,7 @@ import ( "flag" "golang_restful_api/controllers" "golang_restful_api/models" + "golang_restful_api/utils" "log" "net/http" "os" @@ -18,7 +19,7 @@ func main() { "This ensures that a .config file is provided before the application start.") flag.Parse() - cfg := LoadConfig(*boolPtr) + cfg := utils.LoadConfig(*boolPtr) dbCfg := cfg.Database services, err := models.NewServices( models.WithGorm(dbCfg.Dialect(), dbCfg.ConnectionInfo()), diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/services.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/services.go index 38e5396..00762b6 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/services.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/services.go @@ -1,6 +1,7 @@ package models import ( + "fmt" "github.com/jinzhu/gorm" _ "github.com/jinzhu/gorm/dialects/postgres" ) @@ -61,6 +62,12 @@ func (s *Services) Close() error { return s.db.Close() } +// DB return the db connection +func (s *Services) DB() *gorm.DB { + fmt.Println(s.db) + return s.db +} + // DestructiveReset drops all tables and rebuilds them // FOR DEVELOPMENT func (s *Services) DestructiveReset() (error, error) { diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/.configtest b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/.configtest similarity index 92% rename from Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/.configtest rename to Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/.configtest index 3c2dbba..7d781fd 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/.configtest +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/.configtest @@ -4,7 +4,7 @@ "pepper": "secret-random-test-string", "database": { "host": "localhost", - "port": 5433, + "port": 5432, "user": "restapitest", "password": "12345", "dbname": "restapi_test" diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/model_test.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/model_test.go index d8f5794..cf31cd8 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/model_test.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/model_test.go @@ -2,19 +2,76 @@ package modeltests import ( "fmt" + "github.com/jinzhu/gorm" + "golang.org/x/crypto/bcrypt" + "golang_restful_api/models" + "golang_restful_api/utils" + "log" "os" "testing" - "golang_restful_api/main" - "golang_restful_api/models" ) -// TODO: figure out the way to initialize necessary things at very beginning of the test +var services *models.Services +var newdb *gorm.DB +var pepper string + func TestMain(m *testing.M) { - cfg := main.LoadTestConfig() + cfg := utils.LoadTestConfig() dbCfg := cfg.Database - fmt.Println(dbCfg) + services, err := models.NewServices( + models.WithGorm(dbCfg.Dialect(), dbCfg.ConnectionInfo()), + models.WithUser(cfg.Pepper), + models.WithTodo(), + ) + if err != nil { + fmt.Println("[ERROR] Something went wrong with test initialization", err) + } + newdb = services.DB() + pepper = cfg.Pepper os.Exit(m.Run()) } +func refreshUserTable() error { + err := newdb.DropTableIfExists(&models.User{}).Error + if err != nil { + return err + } + err = newdb.AutoMigrate(&models.User{}).Error + if err != nil { + return err + } + log.Printf("Successfully refreshed table") + return nil +} + +func bcryptPassword(user *models.User) error { + if user.Password == "" { + return nil + } + pwBytes := []byte(user.Password + pepper) + hashedBytes, err := bcrypt.GenerateFromPassword(pwBytes, bcrypt.DefaultCost) + if err != nil { + return err + } + user.PasswordHash = string(hashedBytes) + user.Password = "" + return nil +} + +func seedOneUser() (models.User, error) { + user := models.User{ + FirstName: "Shahriyar", + LastName: "Rzayev", + UserName: "shako", + Email: "rzayev.sehriyar@gmail.com", + Password: "12345", + } + bcryptPassword(&user) + err := newdb.Model(&models.User{}).Create(&user).Error + if err != nil { + log.Fatalf("cannot seed users table: %v", err) + } + return user, nil +} diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/user_model_test.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/user_model_test.go index ff4f1d1..f11325d 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/user_model_test.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/user_model_test.go @@ -1,7 +1,10 @@ package modeltests -import "testing" - -func TestFindAllUsers(t *testing.T) { +import ( + "testing" +) +func TestFindSingleUser(t *testing.T) { + refreshUserTable() + seedOneUser() } \ No newline at end of file diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/config.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/utils/config.go similarity index 97% rename from Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/config.go rename to Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/utils/config.go index 2a2a4c9..65df147 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/config.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/utils/config.go @@ -1,4 +1,4 @@ -package main +package utils import ( "encoding/json" @@ -91,6 +91,7 @@ func LoadConfig(configreq bool) Config { return c } +// LoadTestConfig method for parsing .configtest file func LoadTestConfig() Config { f, err := os.Open(".configtest") if err != nil { From 7824966f592a295319ce0584bc5edbaa2805b85e Mon Sep 17 00:00:00 2001 From: Shahriyar Rzayev Date: Mon, 18 May 2020 13:27:27 +0400 Subject: [PATCH 16/30] Added failed test --- .../golang_rest_api/tests/modeltests/user_model_test.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/user_model_test.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/user_model_test.go index f11325d..3653798 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/user_model_test.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/user_model_test.go @@ -5,6 +5,12 @@ import ( ) func TestFindSingleUser(t *testing.T) { + // TODO: failing test refreshUserTable() seedOneUser() + user, _ := services.GetUsers() + length := len(user) + if length !=1 { + t.Errorf("The length of user slice should be 1 but got %d", length) + } } \ No newline at end of file From 1b083815c89805e91cf09c231b3f359ed0dfdecd Mon Sep 17 00:00:00 2001 From: Shahriyar Rzayev Date: Mon, 18 May 2020 19:28:35 +0400 Subject: [PATCH 17/30] First working test is ready --- .../tests/modeltests/model_test.go | 5 +++- .../tests/modeltests/user_model_test.go | 28 +++++++++++++++---- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/model_test.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/model_test.go index cf31cd8..9d7f933 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/model_test.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/model_test.go @@ -16,9 +16,10 @@ var newdb *gorm.DB var pepper string func TestMain(m *testing.M) { + var err error cfg := utils.LoadTestConfig() dbCfg := cfg.Database - services, err := models.NewServices( + services, err = models.NewServices( models.WithGorm(dbCfg.Dialect(), dbCfg.ConnectionInfo()), models.WithUser(cfg.Pepper), models.WithTodo(), @@ -71,7 +72,9 @@ func seedOneUser() (models.User, error) { err := newdb.Model(&models.User{}).Create(&user).Error if err != nil { log.Fatalf("cannot seed users table: %v", err) + return models.User{}, err } + log.Printf("Successfully inserted the user") return user, nil } diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/user_model_test.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/user_model_test.go index 3653798..8f4edae 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/user_model_test.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/user_model_test.go @@ -1,16 +1,32 @@ package modeltests import ( + "fmt" + "log" "testing" ) func TestFindSingleUser(t *testing.T) { - // TODO: failing test - refreshUserTable() - seedOneUser() - user, _ := services.GetUsers() - length := len(user) - if length !=1 { + err := refreshUserTable() + if err != nil { + log.Fatal(err) + } + _, err = seedOneUser() + if err != nil { + log.Fatal(err) + } + fmt.Println(services) + users, err := services.User.GetUsers() + if err != nil { + log.Fatal(err) + return + } + length := len(users) + if length != 1 { t.Errorf("The length of user slice should be 1 but got %d", length) } +} + +func TestFindAllUsers(t *testing.T) { + return } \ No newline at end of file From 66f9d3cfd1ded9814c1883eed5d77279c91ef5dc Mon Sep 17 00:00:00 2001 From: Shahriyar Rzayev Date: Tue, 26 May 2020 00:56:55 +0400 Subject: [PATCH 18/30] Added further tests to test User model at db level --- .../Golang_Gorilla/golang_rest_api/go.mod | 1 + .../golang_rest_api/models/users.go | 13 ++- .../tests/modeltests/user_model_test.go | 97 ++++++++++++++++++- 3 files changed, 99 insertions(+), 12 deletions(-) diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/go.mod b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/go.mod index e65f2de..032a16a 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/go.mod +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/go.mod @@ -11,6 +11,7 @@ require ( github.com/jinzhu/gorm v1.9.12 github.com/kr/pretty v0.1.0 // indirect github.com/leodido/go-urn v1.2.0 // indirect + github.com/stretchr/testify v1.4.0 golang.org/x/crypto v0.0.0-20200420201142-3c4aac89819a gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect gopkg.in/go-playground/assert.v1 v1.2.1 // indirect diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/users.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/users.go index 857889c..c14548d 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/users.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/users.go @@ -8,10 +8,10 @@ import ( // User will hold the user details type User struct { gorm.Model - FirstName string `json:"first_name" validate:"required,min=3,max=15" gorm:"not null"` - LastName string `json:"last_name" validate:"required,min=3,max=20" gorm:"not null"` - UserName string `json:"user_name" validate:"required,min=3,max=10" gorm:"not null"` - Email string `json:"email" validate:"required,email" gorm:"not null;unique_index"` + FirstName string `json:"first_name" validate:"required,min=3,max=15" gorm:"type:varchar(10);not null"` + LastName string `json:"last_name" validate:"required,min=3,max=20" gorm:"type:varchar(15);not null"` + UserName string `json:"user_name" validate:"required,min=3,max=10" gorm:"type:varchar(8);not null"` + Email string `json:"email" validate:"required,email" gorm:"type:varchar(30);not null;unique_index"` Password string `json:"password" validate:"required,min=5,max=15" gorm:"-"` PasswordHash string `json:"-" gorm:"not null;unique_index"` } @@ -148,13 +148,12 @@ func (us *userService) bcryptPassword(user *User) error { return nil } -// Create will create provided user and backfill data -// like the ID, CreatedAt etc. +// AddUser will call actual db command to create the user func (ug *userGorm) AddUser(user *User) error { return ug.db.Create(user).Error } -// CreateUser hash password and then create database +// CreateUser hash password and then create user func (us *userService) CreateUser(user *User) error { err := us.bcryptPassword(user) if err != nil { diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/user_model_test.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/user_model_test.go index 8f4edae..96d0d0e 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/user_model_test.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/user_model_test.go @@ -2,23 +2,24 @@ package modeltests import ( "fmt" - "log" + "github.com/stretchr/testify/assert" + "golang_restful_api/models" "testing" ) -func TestFindSingleUser(t *testing.T) { +func TestIfSingleUserExists(t *testing.T) { err := refreshUserTable() if err != nil { - log.Fatal(err) + t.Fatal(err) } _, err = seedOneUser() if err != nil { - log.Fatal(err) + t.Fatal(err) } fmt.Println(services) users, err := services.User.GetUsers() if err != nil { - log.Fatal(err) + t.Fatal(err) return } length := len(users) @@ -27,6 +28,92 @@ func TestFindSingleUser(t *testing.T) { } } +func TestCreateUserWithDuplicateEmail(t *testing.T) { + err := refreshUserTable() + if err != nil { + t.Fatal(err) + } + _, err = seedOneUser() + if err != nil { + t.Fatal(err) + } + user := models.User{ + FirstName: "Shahriyar", + LastName: "Rzayev", + UserName: "shako", + Email: "rzayev.sehriyar@gmail.com", + Password: "12345", + } + err = services.User.CreateUser(&user) + // Expecting unique constraint fail error message + assert.EqualErrorf(t, err, "pq: duplicate key value violates unique constraint \"uix_users_email\"", + "error message %s", err) +} + +func TestCreateUserWithDuplicateUserName(t *testing.T) { + err := refreshUserTable() + if err != nil { + t.Fatal(err) + } + _, err = seedOneUser() + if err != nil { + t.Fatal(err) + } + user := models.User{ + FirstName: "Shahriyar", + LastName: "Rzayev", + UserName: "shako", + Email: "rzayev.sehriyarrr@gmail.com", + Password: "12345", + } + err = services.User.CreateUser(&user) + // Expecting nil from err as we don't require the username to be unique + assert.Nil(t, err) +} + +func TestUserTableColumnsLength(t *testing.T) { + err := refreshUserTable() + if err != nil { + t.Fatal(err) + } + user := models.User{ + FirstName: "Sjsdaksdajsdlajdlaksjdaljdssaljdsaljdlajsljdaldjsaldjalsjdalksj", + LastName: "Rzayev", + UserName: "shako", + Email: "rzayev.sehriyarrr@gmail.com", + Password: "12345", + } + err = services.User.CreateUser(&user) + assert.EqualErrorf(t, err, "pq: value too long for type character varying(10)", + "error message %s", err) + + // LastName related case + user.LastName = "Sjsdaksdajsdlajdlaksjdaljdssaljdsaljdlajsljdaldjsaldjalsjdalksj" + user.FirstName = "Shahriyar" + err = services.User.CreateUser(&user) + assert.EqualErrorf(t, err, "pq: value too long for type character varying(15)", + "error message %s", err) + + // Checking email length + user.LastName = "Rzayev" + user.Email = "asjdhajsdjabsdjabsdjabsjdbasjbdjasbdjasdb" + err = services.User.CreateUser(&user) + assert.EqualError(t, err, "pq: value too long for type character varying(30)") + + // Checking username length + user.Email = "rzayev.sehriyar@box.az" + user.UserName = "asdsadasda2323423dkasdjnasdnaasd" + err = services.User.CreateUser(&user) + assert.EqualError(t, err, "pq: value too long for type character varying(8)") + +} + +func TestCreateUserWithEmptyPasswordHash(t *testing.T) { + // PasswordHash must not be empty at db level. + // But keep in mind that database does not care the algorithm or some kind of type of our hash. + return +} + func TestFindAllUsers(t *testing.T) { return } \ No newline at end of file From 859e412213e7ff3cd77491236bd2848910d1b200 Mon Sep 17 00:00:00 2001 From: Shahriyar Rzayev Date: Tue, 26 May 2020 01:02:36 +0400 Subject: [PATCH 19/30] fixed the length --- .../Golang_Gorilla/golang_rest_api/models/users.go | 6 +++--- .../golang_rest_api/tests/modeltests/user_model_test.go | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/users.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/users.go index c14548d..0b117fb 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/users.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/users.go @@ -8,9 +8,9 @@ import ( // User will hold the user details type User struct { gorm.Model - FirstName string `json:"first_name" validate:"required,min=3,max=15" gorm:"type:varchar(10);not null"` - LastName string `json:"last_name" validate:"required,min=3,max=20" gorm:"type:varchar(15);not null"` - UserName string `json:"user_name" validate:"required,min=3,max=10" gorm:"type:varchar(8);not null"` + FirstName string `json:"first_name" validate:"required,min=3,max=15" gorm:"type:varchar(15);not null"` + LastName string `json:"last_name" validate:"required,min=3,max=20" gorm:"type:varchar(20);not null"` + UserName string `json:"user_name" validate:"required,min=3,max=10" gorm:"type:varchar(10);not null"` Email string `json:"email" validate:"required,email" gorm:"type:varchar(30);not null;unique_index"` Password string `json:"password" validate:"required,min=5,max=15" gorm:"-"` PasswordHash string `json:"-" gorm:"not null;unique_index"` diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/user_model_test.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/user_model_test.go index 96d0d0e..daf1d81 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/user_model_test.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/user_model_test.go @@ -84,14 +84,14 @@ func TestUserTableColumnsLength(t *testing.T) { Password: "12345", } err = services.User.CreateUser(&user) - assert.EqualErrorf(t, err, "pq: value too long for type character varying(10)", + assert.EqualErrorf(t, err, "pq: value too long for type character varying(15)", "error message %s", err) // LastName related case user.LastName = "Sjsdaksdajsdlajdlaksjdaljdssaljdsaljdlajsljdaldjsaldjalsjdalksj" user.FirstName = "Shahriyar" err = services.User.CreateUser(&user) - assert.EqualErrorf(t, err, "pq: value too long for type character varying(15)", + assert.EqualErrorf(t, err, "pq: value too long for type character varying(20)", "error message %s", err) // Checking email length @@ -104,7 +104,7 @@ func TestUserTableColumnsLength(t *testing.T) { user.Email = "rzayev.sehriyar@box.az" user.UserName = "asdsadasda2323423dkasdjnasdnaasd" err = services.User.CreateUser(&user) - assert.EqualError(t, err, "pq: value too long for type character varying(8)") + assert.EqualError(t, err, "pq: value too long for type character varying(10)") } From 85b5057e43b605bfd304d70deb10954d07f45cc9 Mon Sep 17 00:00:00 2001 From: Shahriyar Rzayev Date: Tue, 26 May 2020 19:25:04 +0400 Subject: [PATCH 20/30] Added EmptyPassword and EmptyPasswordHash tests --- .../golang_rest_api/models/errors.go | 2 + .../golang_rest_api/models/users.go | 4 +- .../tests/modeltests/model_test.go | 11 +++- .../tests/modeltests/user_model_test.go | 55 ++++++++++++++++++- 4 files changed, 66 insertions(+), 6 deletions(-) diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/errors.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/errors.go index 13ac204..b477441 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/errors.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/errors.go @@ -6,6 +6,8 @@ const ( // ErrPasswordIncorrect is returned when an invalid password // is used when attempting to authenticate a user. ErrPasswordIncorrect modelError = "models: incorrect password provided" + // ErrPasswordEmpty + ErrPasswordEmpty modelError = "models: empty password provided" ) type modelError string diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/users.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/users.go index 0b117fb..308b10c 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/users.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/users.go @@ -13,7 +13,7 @@ type User struct { UserName string `json:"user_name" validate:"required,min=3,max=10" gorm:"type:varchar(10);not null"` Email string `json:"email" validate:"required,email" gorm:"type:varchar(30);not null;unique_index"` Password string `json:"password" validate:"required,min=5,max=15" gorm:"-"` - PasswordHash string `json:"-" gorm:"not null;unique_index"` + PasswordHash string `json:"-" gorm:"not null;unique_index;default:null"` } type Login struct { @@ -136,7 +136,7 @@ func (ug *userGorm) UpdateUser(user *User) error { // Password is not the empty string func (us *userService) bcryptPassword(user *User) error { if user.Password == "" { - return nil + return ErrPasswordEmpty } pwBytes := []byte(user.Password + us.pepper) hashedBytes, err := bcrypt.GenerateFromPassword(pwBytes, bcrypt.DefaultCost) diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/model_test.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/model_test.go index 9d7f933..3be2168 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/model_test.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/model_test.go @@ -14,6 +14,15 @@ import ( var services *models.Services var newdb *gorm.DB var pepper string +type testError string + +func (e testError) Error() string { + return string(e) +} + +const ( + ErrPasswordEmpty testError = "testError: empty password provided" +) func TestMain(m *testing.M) { var err error @@ -47,7 +56,7 @@ func refreshUserTable() error { func bcryptPassword(user *models.User) error { if user.Password == "" { - return nil + return ErrPasswordEmpty } pwBytes := []byte(user.Password + pepper) hashedBytes, err := bcrypt.GenerateFromPassword(pwBytes, bcrypt.DefaultCost) diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/user_model_test.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/user_model_test.go index daf1d81..304ad21 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/user_model_test.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/user_model_test.go @@ -87,31 +87,80 @@ func TestUserTableColumnsLength(t *testing.T) { assert.EqualErrorf(t, err, "pq: value too long for type character varying(15)", "error message %s", err) - // LastName related case + // Cehcking LastName length + err = refreshUserTable() + if err != nil { + t.Fatal(err) + } user.LastName = "Sjsdaksdajsdlajdlaksjdaljdssaljdsaljdlajsljdaldjsaldjalsjdalksj" user.FirstName = "Shahriyar" + user.Password = "12345" err = services.User.CreateUser(&user) assert.EqualErrorf(t, err, "pq: value too long for type character varying(20)", "error message %s", err) // Checking email length + err = refreshUserTable() + if err != nil { + t.Fatal(err) + } user.LastName = "Rzayev" user.Email = "asjdhajsdjabsdjabsdjabsjdbasjbdjasbdjasdb" + user.Password = "12345" err = services.User.CreateUser(&user) assert.EqualError(t, err, "pq: value too long for type character varying(30)") // Checking username length + err = refreshUserTable() + if err != nil { + t.Fatal(err) + } user.Email = "rzayev.sehriyar@box.az" user.UserName = "asdsadasda2323423dkasdjnasdnaasd" + user.Password = "12345" err = services.User.CreateUser(&user) assert.EqualError(t, err, "pq: value too long for type character varying(10)") } -func TestCreateUserWithEmptyPasswordHash(t *testing.T) { +func TestCreateUserWithEmptyPassword(t *testing.T) { + err := refreshUserTable() + if err != nil { + t.Fatal(err) + } + user := models.User{ + FirstName: "Shahriyar", + LastName: "Rzayev", + UserName: "shako", + Email: "rzayev.sehriyar@gmail.com", + Password: "", + } + err = services.User.CreateUser(&user) + assert.EqualError(t, err, "models: empty password provided") +} + +func TestAddUserWithEmptyPasswordHash(t *testing.T) { // PasswordHash must not be empty at db level. // But keep in mind that database does not care the algorithm or some kind of type of our hash. - return + err := refreshUserTable() + if err != nil { + t.Fatal(err) + } + user := models.User{ + FirstName: "Shahriyar", + LastName: "Rzayev", + UserName: "shako", + Email: "rzayev.sehriyar@gmail.com", + Password: "12345", + } + err = bcryptPassword(&user) + if err != nil { + t.Fatal(err) + } + // Resetting PasswordHash it should fail to add user with empty PasswordHash. + user.PasswordHash = "" + err = services.User.AddUser(&user) + assert.EqualError(t, err, "pq: null value in column \"password_hash\" violates not-null constraint") } func TestFindAllUsers(t *testing.T) { From 7ef977bf400a4bd57580a41a334192fe47c02591 Mon Sep 17 00:00:00 2001 From: Shahriyar Rzayev Date: Wed, 27 May 2020 12:23:50 +0400 Subject: [PATCH 21/30] Fixing import errors and added empty field tests --- .../golang_rest_api/controllers/delete.go | 4 +- .../golang_rest_api/controllers/get.go | 4 +- .../golang_rest_api/controllers/login.go | 6 +- .../golang_rest_api/controllers/middleware.go | 6 +- .../golang_rest_api/controllers/post.go | 4 +- .../golang_rest_api/controllers/put.go | 4 +- .../golang_rest_api/controllers/todos.go | 6 +- .../golang_rest_api/controllers/users.go | 2 +- .../Golang_Gorilla/golang_rest_api/go.mod | 2 +- .../Golang_Gorilla/golang_rest_api/main.go | 6 +- .../golang_rest_api/models/users.go | 8 +-- .../tests/modeltests/model_test.go | 4 +- .../tests/modeltests/user_model_test.go | 70 ++++++++++++++++++- .../golang_rest_api/utils/utils.go | 2 +- 14 files changed, 98 insertions(+), 30 deletions(-) diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/delete.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/delete.go index 382d7d0..083987e 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/delete.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/delete.go @@ -1,8 +1,8 @@ package controllers import ( - "golang_restful_api/models" - "golang_restful_api/utils" + "golang_rest_api/models" + "golang_rest_api/utils" "net/http" ) diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/get.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/get.go index 4dc4420..0e8e0a1 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/get.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/get.go @@ -1,8 +1,8 @@ package controllers import ( - "golang_restful_api/models" - "golang_restful_api/utils" + "golang_rest_api/models" + "golang_rest_api/utils" "net/http" ) diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/login.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/login.go index dd58b55..cdb260a 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/login.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/login.go @@ -2,9 +2,9 @@ package controllers import ( "fmt" - "golang_restful_api/auth" - "golang_restful_api/models" - "golang_restful_api/utils" + "golang_rest_api/auth" + "golang_rest_api/models" + "golang_rest_api/utils" "net/http" ) diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/middleware.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/middleware.go index 6cfb2ad..3c5f42b 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/middleware.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/middleware.go @@ -6,9 +6,9 @@ import ( "net/http" "regexp" - "golang_restful_api/auth" - "golang_restful_api/models" - "golang_restful_api/utils" + "golang_rest_api/auth" + "golang_rest_api/models" + "golang_rest_api/utils" ) //type genF = func(*GenHandler, http.Handler) http.Handler diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/post.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/post.go index a6ebb55..040256d 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/post.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/post.go @@ -2,8 +2,8 @@ package controllers import ( "fmt" - "golang_restful_api/models" - "golang_restful_api/utils" + "golang_rest_api/models" + "golang_rest_api/utils" "net/http" ) diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/put.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/put.go index 383305c..ff65d29 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/put.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/put.go @@ -1,8 +1,8 @@ package controllers import ( - "golang_restful_api/models" - "golang_restful_api/utils" + "golang_rest_api/models" + "golang_rest_api/utils" "net/http" ) diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/todos.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/todos.go index 5d1d05f..be0d267 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/todos.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/todos.go @@ -2,9 +2,9 @@ package controllers import ( "github.com/gorilla/mux" - "golang_restful_api/auth" - "golang_restful_api/models" - "golang_restful_api/utils" + "golang_rest_api/auth" + "golang_rest_api/models" + "golang_rest_api/utils" "log" "net/http" "strconv" diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/users.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/users.go index dd7a894..34c7b97 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/users.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/users.go @@ -1,7 +1,7 @@ package controllers import ( - "golang_restful_api/models" + "golang_rest_api/models" "log" "net/http" "strconv" diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/go.mod b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/go.mod index 032a16a..8e26b2c 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/go.mod +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/go.mod @@ -1,4 +1,4 @@ -module golang_restful_api +module golang_rest_api go 1.14 diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/main.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/main.go index 6750ebb..e4a4b8f 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/main.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/main.go @@ -2,9 +2,9 @@ package main import ( "flag" - "golang_restful_api/controllers" - "golang_restful_api/models" - "golang_restful_api/utils" + "golang_rest_api/controllers" + "golang_rest_api/models" + "golang_rest_api/utils" "log" "net/http" "os" diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/users.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/users.go index 308b10c..cbba554 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/users.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/users.go @@ -8,10 +8,10 @@ import ( // User will hold the user details type User struct { gorm.Model - FirstName string `json:"first_name" validate:"required,min=3,max=15" gorm:"type:varchar(15);not null"` - LastName string `json:"last_name" validate:"required,min=3,max=20" gorm:"type:varchar(20);not null"` - UserName string `json:"user_name" validate:"required,min=3,max=10" gorm:"type:varchar(10);not null"` - Email string `json:"email" validate:"required,email" gorm:"type:varchar(30);not null;unique_index"` + FirstName string `json:"first_name" validate:"required,min=3,max=15" gorm:"type:varchar(15);not null;default:null"` + LastName string `json:"last_name" validate:"required,min=3,max=20" gorm:"type:varchar(20);not null;default:null"` + UserName string `json:"user_name" validate:"required,min=3,max=10" gorm:"type:varchar(10);not null;default:null"` + Email string `json:"email" validate:"required,email" gorm:"type:varchar(30);not null;unique_index;default:null"` Password string `json:"password" validate:"required,min=5,max=15" gorm:"-"` PasswordHash string `json:"-" gorm:"not null;unique_index;default:null"` } diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/model_test.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/model_test.go index 3be2168..d054626 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/model_test.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/model_test.go @@ -4,8 +4,8 @@ import ( "fmt" "github.com/jinzhu/gorm" "golang.org/x/crypto/bcrypt" - "golang_restful_api/models" - "golang_restful_api/utils" + "golang_rest_api/models" + "golang_rest_api/utils" "log" "os" "testing" diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/user_model_test.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/user_model_test.go index 304ad21..a0b291f 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/user_model_test.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/user_model_test.go @@ -3,7 +3,7 @@ package modeltests import ( "fmt" "github.com/stretchr/testify/assert" - "golang_restful_api/models" + "golang_rest_api/models" "testing" ) @@ -163,6 +163,74 @@ func TestAddUserWithEmptyPasswordHash(t *testing.T) { assert.EqualError(t, err, "pq: null value in column \"password_hash\" violates not-null constraint") } +func TestCreateUserWithEmptyEmail(t *testing.T) { + err := refreshUserTable() + if err != nil { + t.Fatal(err) + } + user := models.User{ + FirstName: "Shahriyar", + LastName: "Rzayev", + UserName: "shako", + Email: "", + Password: "12345", + } + + err = services.User.CreateUser(&user) + assert.EqualError(t, err, "pq: null value in column \"email\" violates not-null constraint") +} + +func TestCreateUserWithEmptyUserName(t *testing.T) { + err := refreshUserTable() + if err != nil { + t.Fatal(err) + } + user := models.User{ + FirstName: "Shahriyar", + LastName: "Rzayev", + UserName: "", + Email: "rzayev.sehriyar@gmail.com", + Password: "12345", + } + + err = services.User.CreateUser(&user) + assert.EqualError(t, err, "pq: null value in column \"user_name\" violates not-null constraint") +} + +func TestCreateUserWithEmptyFirstName(t *testing.T) { + err := refreshUserTable() + if err != nil { + t.Fatal(err) + } + user := models.User{ + FirstName: "", + LastName: "Rzayev", + UserName: "shako", + Email: "rzayev.sehriyar@gmail.com", + Password: "12345", + } + + err = services.User.CreateUser(&user) + assert.EqualError(t, err, "pq: null value in column \"first_name\" violates not-null constraint") +} + +func TestCreateUserWithEmptyLastName(t *testing.T) { + err := refreshUserTable() + if err != nil { + t.Fatal(err) + } + user := models.User{ + FirstName: "Shahriyar", + LastName: "", + UserName: "shako", + Email: "rzayev.sehriyar@gmail.com", + Password: "12345", + } + + err = services.User.CreateUser(&user) + assert.EqualError(t, err, "pq: null value in column \"last_name\" violates not-null constraint") +} + func TestFindAllUsers(t *testing.T) { return } \ No newline at end of file diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/utils/utils.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/utils/utils.go index 6480989..f94dd6b 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/utils/utils.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/utils/utils.go @@ -1,7 +1,7 @@ package utils import ( - "golang_restful_api/models" + "golang_rest_api/models" "net/http" ) From 6fa213e8c3ecb3e24cbbec05c3aaf41b70fed84c Mon Sep 17 00:00:00 2001 From: Shahriyar Rzayev Date: Wed, 27 May 2020 13:54:36 +0400 Subject: [PATCH 22/30] model tests continued --- .../golang_rest_api/models/errors.go | 2 + .../golang_rest_api/models/users.go | 3 + .../tests/modeltests/model_test.go | 28 ++++++ .../tests/modeltests/user_model_test.go | 87 ++++++++++++++++++- 4 files changed, 118 insertions(+), 2 deletions(-) diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/errors.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/errors.go index b477441..27ea15c 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/errors.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/errors.go @@ -6,8 +6,10 @@ const ( // ErrPasswordIncorrect is returned when an invalid password // is used when attempting to authenticate a user. ErrPasswordIncorrect modelError = "models: incorrect password provided" + // The errors below are primarily used in unit tests. // ErrPasswordEmpty ErrPasswordEmpty modelError = "models: empty password provided" + ErrEmailEmpty modelError = "models: empty email address provided" ) type modelError string diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/users.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/users.go index cbba554..c203565 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/users.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/users.go @@ -109,6 +109,9 @@ func (ug *userGorm) GetUsers() ([]*User, error) { // GetUserByEmail Looks up a user with given email address. // returns that user. func (ug *userGorm) GetUserByEmail(email string) (*User, error) { + if email == "" { + return nil, ErrEmailEmpty + } var user User db := ug.db.Where("email = ?", email) err := first(db, &user) diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/model_test.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/model_test.go index d054626..2e84b9f 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/model_test.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/model_test.go @@ -87,3 +87,31 @@ func seedOneUser() (models.User, error) { return user, nil } +func seedTwoUser() ([]models.User, error) { + users := []models.User{ + { + FirstName: "Shahriyar", + LastName: "Rzayev", + UserName: "shako", + Email: "rzayev.sehriyar@gmail.com", + Password: "12345", + }, + { + FirstName: "Tural", + LastName: "Yek", + UserName: "T_Yek", + Email: "tural_yek@gmail.com", + Password: "12345", + }, + } + for _, user := range(users) { + bcryptPassword(&user) + err := newdb.Model(&models.User{}).Create(&user).Error + if err != nil { + log.Fatalf("cannot seed users table: %v", err) + return nil, err + } + } + log.Printf("Successfully inserted the users") + return users, nil +} diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/user_model_test.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/user_model_test.go index a0b291f..5d3aa21 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/user_model_test.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/user_model_test.go @@ -28,6 +28,27 @@ func TestIfSingleUserExists(t *testing.T) { } } +func TestIfTwoUsersExist(t *testing.T) { + err := refreshUserTable() + if err != nil { + t.Fatal(err) + } + _, err = seedTwoUser() + if err != nil { + t.Fatal(err) + } + fmt.Println(services) + users, err := services.User.GetUsers() + if err != nil { + t.Fatal(err) + return + } + length := len(users) + if length != 2 { + t.Errorf("The length of user slice should be 2 but got %d", length) + } +} + func TestCreateUserWithDuplicateEmail(t *testing.T) { err := refreshUserTable() if err != nil { @@ -231,6 +252,68 @@ func TestCreateUserWithEmptyLastName(t *testing.T) { assert.EqualError(t, err, "pq: null value in column \"last_name\" violates not-null constraint") } -func TestFindAllUsers(t *testing.T) { - return +func TestGetUserByEmail(t *testing.T) { + err := refreshUserTable() + if err != nil { + t.Fatal(err) + } + + // Trying to get user with empty email address + // It should fail with ErrEmailEmpty models error. + _, err = services.User.GetUserByEmail("") + assert.EqualError(t, err, "models: empty email address provided") + + _, err = seedOneUser() + if err != nil { + t.Fatal(err) + } + + // Trying to get user with non-existing email address. + // It should fail with ErrNotFound models error + _, err = services.User.GetUserByEmail("rzayev@box.az") + assert.EqualError(t, err, "models: resource not found") + + // Trying to get user with existing email address. + // It should return the found user. I will check if returned user's email is equal to passed email. + user, err := services.User.GetUserByEmail("rzayev.sehriyar@gmail.com") + assert.Equal(t, "rzayev.sehriyar@gmail.com", user.Email) +} + +func TestGetUserByID(t *testing.T) { + err := refreshUserTable() + if err != nil { + t.Fatal(err) + } + + _, err = seedOneUser() + if err != nil { + t.Fatal(err) + } + + // Trying to get user with non-existing ID + // Should return an ErrNotFound + _, err = services.User.GetUserByID(222) + assert.EqualError(t, err, "models: resource not found") + + // Trying to get user with existing ID + // Should return found user from DB and I will check if ID is equal 1 or not. + user, err := services.User.GetUserByID(1) + assert.Equal(t, uint(1), user.ID) +} + +func TestDeleteUser(t *testing.T) { + err := refreshUserTable() + if err != nil { + t.Fatal(err) + } + + _, err = seedTwoUser() + if err != nil { + t.Fatal(err) + } + + users, err := services.User.GetUsers() + assert.Equal(t,2, len(users)) + // TODO: need to be developed further + } \ No newline at end of file From 0d4a312deb67b177f31fb5a2b9702b91e294aaf2 Mon Sep 17 00:00:00 2001 From: Shahriyar Rzayev Date: Sat, 30 May 2020 13:58:24 +0400 Subject: [PATCH 23/30] Added test for DeleteUser --- .../tests/modeltests/user_model_test.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/user_model_test.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/user_model_test.go index 5d3aa21..5119f31 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/user_model_test.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/user_model_test.go @@ -314,6 +314,14 @@ func TestDeleteUser(t *testing.T) { users, err := services.User.GetUsers() assert.Equal(t,2, len(users)) - // TODO: need to be developed further + // Trying to delete user with non-existing id + // Expecting error here + err = services.User.DeleteUser(85) + assert.EqualError(t, err, "models: resource not found") + + // Trying to delete first user. + // The delete function should return nil if the user is deleted successfully. + err = services.User.DeleteUser(1) + assert.Equal(t,nil, err) } \ No newline at end of file From 33d0811311f039e0773a4b82d796ab38c619fe1f Mon Sep 17 00:00:00 2001 From: Shahriyar Rzayev Date: Mon, 1 Jun 2020 18:51:00 +0400 Subject: [PATCH 24/30] Added several tests for testing Todo model --- .../golang_rest_api/models/todos.go | 4 +- .../tests/modeltests/model_test.go | 65 ++++++- .../tests/modeltests/todo_model_test.go | 161 ++++++++++++++++++ .../tests/modeltests/user_model_test.go | 23 +++ 4 files changed, 250 insertions(+), 3 deletions(-) create mode 100644 Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/todo_model_test.go diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/todos.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/todos.go index 6e82f8d..1fdbdc6 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/todos.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/todos.go @@ -6,8 +6,8 @@ import ( type Todo struct { gorm.Model - Title string `json:"title" validate:"required,min=5,max=20" gorm:"not null"` - Description string `json:"description" validate:"required,min=10,max=100" gorm:"not null"` + Title string `json:"title" validate:"required,min=5,max=20" gorm:"type:varchar(20);not null;default:null"` + Description string `json:"description" validate:"required,min=10,max=100" gorm:"type:varchar(100);not null;default:null"` UserID uint `json:"-" gorm:"not null"` } diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/model_test.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/model_test.go index 2e84b9f..375566b 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/model_test.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/model_test.go @@ -42,10 +42,14 @@ func TestMain(m *testing.M) { } func refreshUserTable() error { - err := newdb.DropTableIfExists(&models.User{}).Error + // Dropping using SQL statement as it fails otherwise to handle + err := newdb.Exec("DROP TABLE IF EXISTS users CASCADE").Error + //err := newdb.Set("gorm:table_options", "CASCADE").DropTableIfExists(&models.User{}).Error + //err := newdb.DropTableIfExists(&models.User{}).Error if err != nil { return err } + err = newdb.AutoMigrate(&models.User{}).Error if err != nil { return err @@ -54,6 +58,20 @@ func refreshUserTable() error { return nil } +func refreshTodoTable() error { + err := newdb.Set("gorm:table_options", "CASCADE").DropTableIfExists(&models.Todo{}).Error + if err != nil { + return err + } + + err = newdb.AutoMigrate(&models.Todo{}).AddForeignKey("user_id", "users(id)", "CASCADE", "CASCADE").Error + if err != nil { + return err + } + log.Printf("Successfully refreshed table") + return nil +} + func bcryptPassword(user *models.User) error { if user.Password == "" { return ErrPasswordEmpty @@ -115,3 +133,48 @@ func seedTwoUser() ([]models.User, error) { log.Printf("Successfully inserted the users") return users, nil } + + +func seedOneTodo() (models.Todo, error){ + // We assume that seedOneUser() function will be called prior to this function call. + // due to this UserID will be updated to 1 every time. + + todo := models.Todo{ + Title: "Dummy Todo", + Description: "My dummy Todo for tests", + UserID: 1, + } + err := newdb.Model(&models.Todo{}).Create(&todo).Error + if err != nil { + log.Fatalf("cannot seed todo table: %v", err) + return models.Todo{}, err + } + log.Printf("Successfully inserted the todo") + return todo, nil +} + +func seedTwoTodos() ([]models.Todo, error){ + // We assume that seedOneUser() function will be called prior to this function call. + // due to this UserID will be updated to 1 every time. + todos := []models.Todo{ + { + Title: "Dummy Todo", + Description: "My dummy Todo for tests", + UserID: 1, + }, + { + Title: "Dummy Todo 2 ", + Description: "My dummy Todo for tests 2", + UserID: 1, + }, + } + for _, todo := range todos { + err := newdb.Model(&models.Todo{}).Create(&todo).Error + if err != nil { + log.Fatalf("cannot seed todo table: %v", err) + return []models.Todo{}, err + } + } + log.Printf("Successfully inserted the todos") + return todos, nil +} \ No newline at end of file diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/todo_model_test.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/todo_model_test.go new file mode 100644 index 0000000..40b9a39 --- /dev/null +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/todo_model_test.go @@ -0,0 +1,161 @@ +package modeltests + +import ( + "github.com/stretchr/testify/assert" + "golang_rest_api/models" + "testing" +) + +func TestCreateEmptyTodo(t *testing.T) { + err := refreshUserTable() + if err != nil { + t.Fatal(err) + } + + err = refreshTodoTable() + if err != nil { + t.Fatal(err) + } + + todo := models.Todo{} + // It should violate foreign key constraint and as usual should fail at db level. + err = services.Todo.AddTodo(&todo) + assert.EqualError(t, err, "pq: insert or update on table \"todos\" violates foreign key constraint \"todos_user_id_users_id_foreign\"") +} + +func TestCreateTodoWithNonExistingUserID(t *testing.T) { + err := refreshUserTable() + if err != nil { + t.Fatal(err) + } + + err = refreshTodoTable() + if err != nil { + t.Fatal(err) + } + + todo := models.Todo{ + Title: "Dummy Todo", + Description: "My dummy Todo for tests", + UserID: 85, + } + err = services.Todo.AddTodo(&todo) + assert.EqualError(t, err, "pq: insert or update on table \"todos\" violates foreign key constraint \"todos_user_id_users_id_foreign\"") + +} + +func TestCreateTodoWithExistingUserID(t *testing.T) { + err := refreshUserTable() + if err != nil { + t.Fatal(err) + } + + err = refreshTodoTable() + if err != nil { + t.Fatal(err) + } + + _, err = seedOneUser() + if err != nil { + t.Fatal(err) + } + + todo, err := seedOneTodo() + if err != nil { + t.Fatal(err) + } + assert.Equal(t, "Dummy Todo", todo.Title) +} + +func TestTodoTableColumnLengths(t *testing.T) { + err := refreshUserTable() + if err != nil { + t.Fatal(err) + } + + err = refreshTodoTable() + if err != nil { + t.Fatal(err) + } + + todo := models.Todo{ + Title: "Dummy Todoakjdaksdjaklsjdajsdjajdalkdjalksdjalkdjaldjalkdjalsjdaldjalsdkjaslkdj", + Description: "My dummy Todo for tests", + UserID: 85, + } + err = services.Todo.AddTodo(&todo) + assert.EqualError(t, err, "pq: value too long for type character varying(20)") + + todo.Title = "My Todo" + todo.Description = "DummyTodoakjdaksdjaklsjdajsdjajdalkdjalksdjalkdjaldjalkdjalsjdaldjalsdkjaslkdjasdadaddasdasdada" + + "asdasdadsdsadadsaaaaaaaaaaaaaaaaaaasdadadadadadsadadadadasdadadasdasdadadadasdasdasdasd" + + err = services.Todo.AddTodo(&todo) + assert.EqualError(t, err, "pq: value too long for type character varying(100)") +} + +func TestCreateTodoWithEmptyValues(t *testing.T) { + err := refreshUserTable() + if err != nil { + t.Fatal(err) + } + + err = refreshTodoTable() + if err != nil { + t.Fatal(err) + } + + _, err = seedOneUser() + if err != nil { + t.Fatal(err) + } + + todo := models.Todo{ + Title: "", + Description: "My dummy Todo for tests", + UserID: 1, + } + err = services.Todo.AddTodo(&todo) + assert.EqualError(t, err, "pq: null value in column \"title\" violates not-null constraint") + + todo = models.Todo{ + Title: "My dummy Todo", + Description: "", + UserID: 1, + } + err = services.Todo.AddTodo(&todo) + assert.EqualError(t, err, "pq: null value in column \"description\" violates not-null constraint") +} + +func TestGetTodos(t *testing.T) { + err := refreshUserTable() + if err != nil { + t.Fatal(err) + } + + err = refreshTodoTable() + if err != nil { + t.Fatal(err) + } + + user, err := seedOneUser() + if err != nil { + t.Fatal(err) + } + // Get directly + todos, err := seedTwoTodos() + length := len(todos) + assert.Equal(t, 2, length) + + // Get todos using user object + todosSlice, err := services.Todo.GetTodos(&user) + length = len(todosSlice) + assert.Equal(t, 2, length) + + // Get todos with empty user + var emptyUser models.User + todosSlice, err = services.Todo.GetTodos(&emptyUser) + // it should return empty list + assert.Equal(t, 0, len(todosSlice)) + +} diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/user_model_test.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/user_model_test.go index 5119f31..f170d40 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/user_model_test.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/user_model_test.go @@ -324,4 +324,27 @@ func TestDeleteUser(t *testing.T) { // The delete function should return nil if the user is deleted successfully. err = services.User.DeleteUser(1) assert.Equal(t,nil, err) +} + +func TestUpdateUser(t *testing.T) { + err := refreshUserTable() + if err != nil { + t.Fatal(err) + } + + _, err = seedOneUser() + if err != nil { + t.Fatal(err) + } + + user, _ := services.User.GetUserByID(1) + user.Email = "rzayev@box.az" + user.UserName = "srzayev" + + err = services.User.UpdateUser(user) + assert.Equal(t, nil, err) + + user, _ = services.User.GetUserByID(1) + assert.Equal(t, "rzayev@box.az", user.Email) + assert.Equal(t, "srzayev", user.UserName) } \ No newline at end of file From 25782a714dc76d032f32d7948eb590cd341e2df6 Mon Sep 17 00:00:00 2001 From: Shahriyar Rzayev Date: Tue, 2 Jun 2020 00:41:39 +0400 Subject: [PATCH 25/30] finalized todo models tests --- .../golang_rest_api/models/todos.go | 8 +- .../tests/modeltests/todo_model_test.go | 114 +++++++++++++++--- 2 files changed, 101 insertions(+), 21 deletions(-) diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/todos.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/todos.go index 1fdbdc6..0a16bcf 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/todos.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/todos.go @@ -58,12 +58,11 @@ func (tg *todoGorm) GetTodoByID(user *User, id uint) (*Todo, error) { var todo Todo var todos []*Todo db := tg.db.Model(&user).Related(&todos).Where("id = ?", id) - //db := tg.db.Where("id = ?", id) err := first(db, &todo) return &todo, err } -// Update the record in todo table i.e given new todo model +// Update the record in to-do table i.e given new to-do model func (tg *todoGorm) UpdateTodo(user *User, todo *Todo, id uint) error { todos, err := tg.GetTodoByID(user, id) if err != nil { @@ -73,7 +72,7 @@ func (tg *todoGorm) UpdateTodo(user *User, todo *Todo, id uint) error { return tg.db.Save(todos).Error } -// Create will create provided todo and backfill data +// Create will create provided to-do and back fill data // like the ID, CreatedAt etc. func (tg *todoGorm) AddTodo(todo *Todo) error { return tg.db.Create(todo).Error @@ -85,6 +84,5 @@ func (tg *todoGorm) DeleteTodo(user *User, id uint) error { if err != nil { return err } - tg.db.Delete(&todo) - return nil + return tg.db.Delete(&todo).Error } diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/todo_model_test.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/todo_model_test.go index 40b9a39..b226f15 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/todo_model_test.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/todo_model_test.go @@ -6,22 +6,6 @@ import ( "testing" ) -func TestCreateEmptyTodo(t *testing.T) { - err := refreshUserTable() - if err != nil { - t.Fatal(err) - } - - err = refreshTodoTable() - if err != nil { - t.Fatal(err) - } - - todo := models.Todo{} - // It should violate foreign key constraint and as usual should fail at db level. - err = services.Todo.AddTodo(&todo) - assert.EqualError(t, err, "pq: insert or update on table \"todos\" violates foreign key constraint \"todos_user_id_users_id_foreign\"") -} func TestCreateTodoWithNonExistingUserID(t *testing.T) { err := refreshUserTable() @@ -159,3 +143,101 @@ func TestGetTodos(t *testing.T) { assert.Equal(t, 0, len(todosSlice)) } + +func TestGetTodoByID(t *testing.T) { + err := refreshUserTable() + if err != nil { + t.Fatal(err) + } + + err = refreshTodoTable() + if err != nil { + t.Fatal(err) + } + + user, err := seedOneUser() + if err != nil { + t.Fatal(err) + } + + todo, err := seedOneTodo() + if err != nil { + t.Fatal(err) + } + todoReturned, err := services.Todo.GetTodoByID(&user, todo.ID) + // As we have inserted only one to-do here it should have id 1 + assert.Equal(t, uint(1), todoReturned.ID) + + // Get to-do by non-existing ID + // It should raise error models: resource not found + _, err = services.Todo.GetTodoByID(&user, 85) + assert.EqualError(t, err, "models: resource not found") +} + +func TestDeleteTodo(t *testing.T) { + err := refreshUserTable() + if err != nil { + t.Fatal(err) + } + + err = refreshTodoTable() + if err != nil { + t.Fatal(err) + } + + // Try to delete with empty user and to-do ID + todo := models.Todo{} + user := models.User{} + err = services.Todo.DeleteTodo(&user, todo.ID) + assert.EqualError(t, err, "models: resource not found") + + user, err = seedOneUser() + if err != nil { + t.Fatal(err) + } + + todo, err = seedOneTodo() + if err != nil { + t.Fatal(err) + } + + _ = services.Todo.DeleteTodo(&user, todo.ID) + // If delete succeeded searching for to-do id 1 should fail. + _, err = services.Todo.GetTodoByID(&user, 1) + assert.EqualError(t, err, "models: resource not found") +} + +func TestUpdateTodo(t *testing.T) { + err := refreshUserTable() + if err != nil { + t.Fatal(err) + } + + err = refreshTodoTable() + if err != nil { + t.Fatal(err) + } + + user, err := seedOneUser() + if err != nil { + t.Fatal(err) + } + + _, err = seedOneTodo() + if err != nil { + t.Fatal(err) + } + + todo, err := services.Todo.GetTodoByID(&user, 1) + todo.Title = "Updated Todo" + err = services.Todo.UpdateTodo(&user, todo, 1) + assert.Equal(t, nil, err) + todoUpdated, err := services.Todo.GetTodoByID(&user, 1) + assert.Equal(t, "Updated Todo", todoUpdated.Title) + + todo.ID = 5 + err = services.Todo.UpdateTodo(&user, todo, 1) + assert.Equal(t, nil, err) + todoUpdated, err = services.Todo.GetTodoByID(&user, 5) + assert.Equal(t, uint(5), todoUpdated.ID) +} From 0f3649579ce3c57e5196975d06d350479da3213e Mon Sep 17 00:00:00 2001 From: Shahriyar Rzayev Date: Tue, 2 Jun 2020 18:12:40 +0400 Subject: [PATCH 26/30] Started to write controller tests --- .../tests/controllertests/controller_test.go | 181 ++++++++++++++++++ .../controllertests/login_controller_test.go | 3 + 2 files changed, 184 insertions(+) create mode 100644 Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/controllertests/controller_test.go create mode 100644 Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/controllertests/login_controller_test.go diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/controllertests/controller_test.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/controllertests/controller_test.go new file mode 100644 index 0000000..72f1405 --- /dev/null +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/controllertests/controller_test.go @@ -0,0 +1,181 @@ +package controllertests +// Duplicated code with model_test.go file + +import ( +"fmt" +"github.com/jinzhu/gorm" +"golang.org/x/crypto/bcrypt" +"golang_rest_api/models" +"golang_rest_api/utils" +"log" +"os" +"testing" +) + +var services *models.Services +var newdb *gorm.DB +var pepper string +type testError string + +func (e testError) Error() string { + return string(e) +} + +const ( + ErrPasswordEmpty testError = "testError: empty password provided" +) + +func TestMain(m *testing.M) { + var err error + cfg := utils.LoadTestConfig() + dbCfg := cfg.Database + services, err = models.NewServices( + models.WithGorm(dbCfg.Dialect(), dbCfg.ConnectionInfo()), + models.WithUser(cfg.Pepper), + models.WithTodo(), + ) + if err != nil { + fmt.Println("[ERROR] Something went wrong with test initialization", err) + } + newdb = services.DB() + pepper = cfg.Pepper + os.Exit(m.Run()) +} + +func refreshUserTable() error { + // Dropping using SQL statement as it fails otherwise to handle + err := newdb.Exec("DROP TABLE IF EXISTS users CASCADE").Error + //err := newdb.Set("gorm:table_options", "CASCADE").DropTableIfExists(&models.User{}).Error + //err := newdb.DropTableIfExists(&models.User{}).Error + if err != nil { + return err + } + + err = newdb.AutoMigrate(&models.User{}).Error + if err != nil { + return err + } + log.Printf("Successfully refreshed table") + return nil +} + +func refreshTodoTable() error { + err := newdb.Set("gorm:table_options", "CASCADE").DropTableIfExists(&models.Todo{}).Error + if err != nil { + return err + } + + err = newdb.AutoMigrate(&models.Todo{}).AddForeignKey("user_id", "users(id)", "CASCADE", "CASCADE").Error + if err != nil { + return err + } + log.Printf("Successfully refreshed table") + return nil +} + +func bcryptPassword(user *models.User) error { + if user.Password == "" { + return ErrPasswordEmpty + } + pwBytes := []byte(user.Password + pepper) + hashedBytes, err := bcrypt.GenerateFromPassword(pwBytes, bcrypt.DefaultCost) + if err != nil { + return err + } + user.PasswordHash = string(hashedBytes) + user.Password = "" + return nil +} + +func seedOneUser() (models.User, error) { + user := models.User{ + FirstName: "Shahriyar", + LastName: "Rzayev", + UserName: "shako", + Email: "rzayev.sehriyar@gmail.com", + Password: "12345", + } + bcryptPassword(&user) + + err := newdb.Model(&models.User{}).Create(&user).Error + if err != nil { + log.Fatalf("cannot seed users table: %v", err) + return models.User{}, err + } + log.Printf("Successfully inserted the user") + return user, nil +} + +func seedTwoUser() ([]models.User, error) { + users := []models.User{ + { + FirstName: "Shahriyar", + LastName: "Rzayev", + UserName: "shako", + Email: "rzayev.sehriyar@gmail.com", + Password: "12345", + }, + { + FirstName: "Tural", + LastName: "Yek", + UserName: "T_Yek", + Email: "tural_yek@gmail.com", + Password: "12345", + }, + } + for _, user := range(users) { + bcryptPassword(&user) + err := newdb.Model(&models.User{}).Create(&user).Error + if err != nil { + log.Fatalf("cannot seed users table: %v", err) + return nil, err + } + } + log.Printf("Successfully inserted the users") + return users, nil +} + + +func seedOneTodo() (models.Todo, error){ + // We assume that seedOneUser() function will be called prior to this function call. + // due to this UserID will be updated to 1 every time. + + todo := models.Todo{ + Title: "Dummy Todo", + Description: "My dummy Todo for tests", + UserID: 1, + } + err := newdb.Model(&models.Todo{}).Create(&todo).Error + if err != nil { + log.Fatalf("cannot seed todo table: %v", err) + return models.Todo{}, err + } + log.Printf("Successfully inserted the todo") + return todo, nil +} + +func seedTwoTodos() ([]models.Todo, error){ + // We assume that seedOneUser() function will be called prior to this function call. + // due to this UserID will be updated to 1 every time. + todos := []models.Todo{ + { + Title: "Dummy Todo", + Description: "My dummy Todo for tests", + UserID: 1, + }, + { + Title: "Dummy Todo 2 ", + Description: "My dummy Todo for tests 2", + UserID: 1, + }, + } + for _, todo := range todos { + err := newdb.Model(&models.Todo{}).Create(&todo).Error + if err != nil { + log.Fatalf("cannot seed todo table: %v", err) + return []models.Todo{}, err + } + } + log.Printf("Successfully inserted the todos") + return todos, nil +} \ No newline at end of file diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/controllertests/login_controller_test.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/controllertests/login_controller_test.go new file mode 100644 index 0000000..87f7f1a --- /dev/null +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/controllertests/login_controller_test.go @@ -0,0 +1,3 @@ +package controllertests +// Reference blog post +// https://levelup.gitconnected.com/crud-restful-api-with-go-gorm-jwt-postgres-mysql-and-testing-460a85ab7121 From 022a9fb3e06e2356acdf880fdacbd7a23dfc8906 Mon Sep 17 00:00:00 2001 From: Shahriyar Rzayev Date: Thu, 4 Jun 2020 00:24:11 +0400 Subject: [PATCH 27/30] Added first login test --- .../golang_rest_api/controllers/login.go | 2 - .../golang_rest_api/controllers/middleware.go | 9 +- .../tests/controllertests/.configtest | 12 ++ .../tests/controllertests/controller_test.go | 21 +++- .../controllertests/login_controller_test.go | 109 ++++++++++++++++++ 5 files changed, 145 insertions(+), 8 deletions(-) create mode 100644 Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/controllertests/.configtest diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/login.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/login.go index cdb260a..ce83c8d 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/login.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/login.go @@ -1,7 +1,6 @@ package controllers import ( - "fmt" "golang_rest_api/auth" "golang_rest_api/models" "golang_rest_api/utils" @@ -11,7 +10,6 @@ import ( type KeyLogin struct{} func (us *Users) Login(w http.ResponseWriter, r *http.Request) { - fmt.Println("Inside Login handler") login := r.Context().Value(KeyLogin{}).(*models.Login) foundUser, err := us.us.Authenticate(login.Email, login.Password) if err != nil { diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/middleware.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/middleware.go index 3c5f42b..6bb7d84 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/middleware.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/middleware.go @@ -2,7 +2,6 @@ package controllers import ( "context" - "fmt" "net/http" "regexp" @@ -78,27 +77,27 @@ func (g *GenHandler) MiddlewareValidate(next http.Handler) http.Handler { func (g *GenHandler) MiddlewareValidateLogin(next http.Handler, w http.ResponseWriter, r *http.Request) { login := &models.Login{} - fmt.Println(r.Body) err := models.FromJSON(login, r.Body) if err != nil { - g.Users.l.Println("[ERROR] deserializing user login credentials", err) - + g.Users.l.Println("[ERROR] deserialize user login credentials", err) w.WriteHeader(http.StatusBadRequest) utils.Respond(w, &GenericError{Message: err.Error()}) return } + g.Users.l.Println("Pre-Validation: ", login) + // validate the user errs := g.Users.v.Validate(login) if len(errs) != 0 { g.Users.l.Println("[ERROR] validating user login credentials", errs) - // return the validation messages as an array w.WriteHeader(http.StatusBadRequest) utils.Respond(w, &ValidationError{Messages: errs.Errors()}) return } // Add login credentials to the context. + g.Users.l.Println("Post-Validation: ", login) ctx := context.WithValue(r.Context(), KeyLogin{}, login) r = r.WithContext(ctx) // Call the next handler, which can be another middleware in the chain, or the final handler. diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/controllertests/.configtest b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/controllertests/.configtest new file mode 100644 index 0000000..7d781fd --- /dev/null +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/controllertests/.configtest @@ -0,0 +1,12 @@ +{ + "port": ":8080", + "env": "test", + "pepper": "secret-random-test-string", + "database": { + "host": "localhost", + "port": 5432, + "user": "restapitest", + "password": "12345", + "dbname": "restapi_test" + } +} diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/controllertests/controller_test.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/controllertests/controller_test.go index 72f1405..94d4497 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/controllertests/controller_test.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/controllertests/controller_test.go @@ -5,7 +5,8 @@ import ( "fmt" "github.com/jinzhu/gorm" "golang.org/x/crypto/bcrypt" -"golang_rest_api/models" + "golang_rest_api/controllers" + "golang_rest_api/models" "golang_rest_api/utils" "log" "os" @@ -15,6 +16,9 @@ import ( var services *models.Services var newdb *gorm.DB var pepper string +var ah *controllers.Users +var th *controllers.Todos +var gh controllers.GenHandler type testError string func (e testError) Error() string { @@ -39,6 +43,21 @@ func TestMain(m *testing.M) { } newdb = services.DB() pepper = cfg.Pepper + + l := log.New(os.Stdout, "users-api -> ", log.LstdFlags) + v := models.NewValidation() + us := services.User + + l_todo := log.New(os.Stdout, "todos-api -> ", log.LstdFlags) + ts := services.Todo + // create the handler/conrollers + ah = controllers.NewUsers(l, v, us) + th = controllers.NewTodos(l_todo, v, ts, us) + gh = controllers.GenHandler{ + ah, + th, + } + os.Exit(m.Run()) } diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/controllertests/login_controller_test.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/controllertests/login_controller_test.go index 87f7f1a..539d002 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/controllertests/login_controller_test.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/controllertests/login_controller_test.go @@ -1,3 +1,112 @@ package controllertests + +import ( + "bytes" + "encoding/json" + "github.com/gorilla/mux" + "github.com/stretchr/testify/assert" + "golang_rest_api/controllers" + "net/http/httptest" + "testing" +) + // Reference blog post // https://levelup.gitconnected.com/crud-restful-api-with-go-gorm-jwt-postgres-mysql-and-testing-460a85ab7121 + +func TestLoginWithValidationPassWrongJSONFormat(t *testing.T) { + err := refreshUserTable() + if err != nil { + t.Fatal(err) + } + + _, err = seedOneUser() + if err != nil { + t.Fatal(err) + } + + samples := []struct { + inputJSON string + statusCode int + email string + password string + errorMessage string + }{ + { + // the expected fail due to shitty json format + inputJSON: `{"email": "rzayev.sehriyar@gmail.com", "password": 12345"}`, + statusCode: 400, + errorMessage: "", + }, + { + // the expected fail due to shitty json format + inputJSON: `{"email": rzayev.sehriyar@gmail.com, "password": "12345"}`, + statusCode: 400, + errorMessage: "", + }, + { + // the expected fail due to shitty json format + // TODO: figure out how to deal wrong passed KEY name + inputJSON: `{"em": "rzayev.sehriyar@gmail.com", "password": "12345"}`, + statusCode: 200, + errorMessage: "", + }, + //{ + // inputJSON: `{"email": "pet@gmail.com", "password": "wrong password"}`, + // statusCode: 422, + // errorMessage: "Incorrect Password", + //}, + //{ + // inputJSON: `{"email": "frank@gmail.com", "password": "password"}`, + // statusCode: 422, + // errorMessage: "Incorrect Details", + //}, + //{ + // inputJSON: `{"email": "kangmail.com", "password": "password"}`, + // statusCode: 422, + // errorMessage: "Invalid Email", + //}, + //{ + // inputJSON: `{"email": "", "password": "password"}`, + // statusCode: 422, + // errorMessage: "Required Email", + //}, + //{ + // inputJSON: `{"email": "kan@gmail.com", "password": ""}`, + // statusCode: 422, + // errorMessage: "Required Password", + //}, + //{ + // inputJSON: `{"email": "", "password": "password"}`, + // statusCode: 422, + // errorMessage: "Required Email", + //}, + } + + w := httptest.NewRecorder() + r := mux.NewRouter() + r.Use(controllers.CommonMiddleware) + var apiV1 = r.PathPrefix("/api/v1/").Subrouter() + postR := apiV1.Methods("POST").Subrouter() + postR.HandleFunc("/users/login", ah.Login) + postR.Use(gh.MiddlewareValidate) + + for _, v := range samples { + t.Log(v.inputJSON) + req := httptest.NewRequest("POST", "/api/v1/users/login", bytes.NewBufferString(v.inputJSON)) + r.ServeHTTP(w, req) + + assert.Equal(t, w.Code, v.statusCode) + if v.statusCode == 200 { + assert.NotEqual(t, w.Body.String(), "") + } + + if v.statusCode == 422 && v.errorMessage != "" { + responseMap := make(map[string]interface{}) + err = json.Unmarshal([]byte(w.Body.String()), &responseMap) + if err != nil { + t.Errorf("Cannot convert to json: %v", err) + } + assert.Equal(t, responseMap["error"], v.errorMessage) + } + } +} \ No newline at end of file From 953889f2bc9e6c3be84eeeb3383d6eb5a76098fc Mon Sep 17 00:00:00 2001 From: Shahriyar Rzayev Date: Thu, 4 Jun 2020 18:09:47 +0400 Subject: [PATCH 28/30] TestLoginWithValidationPassWrongJSONFormat completed --- .../golang_rest_api/controllers/middleware.go | 8 +-- .../controllertests/login_controller_test.go | 54 +------------------ 2 files changed, 2 insertions(+), 60 deletions(-) diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/middleware.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/middleware.go index 6bb7d84..db9664c 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/middleware.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/middleware.go @@ -84,9 +84,6 @@ func (g *GenHandler) MiddlewareValidateLogin(next http.Handler, w http.ResponseW utils.Respond(w, &GenericError{Message: err.Error()}) return } - g.Users.l.Println("Pre-Validation: ", login) - - // validate the user errs := g.Users.v.Validate(login) if len(errs) != 0 { @@ -97,11 +94,8 @@ func (g *GenHandler) MiddlewareValidateLogin(next http.Handler, w http.ResponseW return } // Add login credentials to the context. - g.Users.l.Println("Post-Validation: ", login) ctx := context.WithValue(r.Context(), KeyLogin{}, login) r = r.WithContext(ctx) - // Call the next handler, which can be another middleware in the chain, or the final handler. - // TODO: this will fail next.ServeHTTP(w, r) return } @@ -111,7 +105,7 @@ func (g *GenHandler) MiddlewareValidateUser(next http.Handler, w http.ResponseWr acc := &models.User{} err := models.FromJSON(acc, r.Body) if err != nil { - g.Users.l.Println("[ERROR] deserializing user", err) + g.Users.l.Println("[ERROR] deserialize user", err) w.WriteHeader(http.StatusBadRequest) utils.Respond(w, &GenericError{Message: err.Error()}) diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/controllertests/login_controller_test.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/controllertests/login_controller_test.go index 539d002..a9d4c6c 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/controllertests/login_controller_test.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/controllertests/login_controller_test.go @@ -2,7 +2,6 @@ package controllertests import ( "bytes" - "encoding/json" "github.com/gorilla/mux" "github.com/stretchr/testify/assert" "golang_rest_api/controllers" @@ -27,59 +26,22 @@ func TestLoginWithValidationPassWrongJSONFormat(t *testing.T) { samples := []struct { inputJSON string statusCode int - email string - password string - errorMessage string }{ { // the expected fail due to shitty json format inputJSON: `{"email": "rzayev.sehriyar@gmail.com", "password": 12345"}`, statusCode: 400, - errorMessage: "", }, { // the expected fail due to shitty json format inputJSON: `{"email": rzayev.sehriyar@gmail.com, "password": "12345"}`, statusCode: 400, - errorMessage: "", }, { // the expected fail due to shitty json format - // TODO: figure out how to deal wrong passed KEY name inputJSON: `{"em": "rzayev.sehriyar@gmail.com", "password": "12345"}`, - statusCode: 200, - errorMessage: "", + statusCode: 400, }, - //{ - // inputJSON: `{"email": "pet@gmail.com", "password": "wrong password"}`, - // statusCode: 422, - // errorMessage: "Incorrect Password", - //}, - //{ - // inputJSON: `{"email": "frank@gmail.com", "password": "password"}`, - // statusCode: 422, - // errorMessage: "Incorrect Details", - //}, - //{ - // inputJSON: `{"email": "kangmail.com", "password": "password"}`, - // statusCode: 422, - // errorMessage: "Invalid Email", - //}, - //{ - // inputJSON: `{"email": "", "password": "password"}`, - // statusCode: 422, - // errorMessage: "Required Email", - //}, - //{ - // inputJSON: `{"email": "kan@gmail.com", "password": ""}`, - // statusCode: 422, - // errorMessage: "Required Password", - //}, - //{ - // inputJSON: `{"email": "", "password": "password"}`, - // statusCode: 422, - // errorMessage: "Required Email", - //}, } w := httptest.NewRecorder() @@ -91,22 +53,8 @@ func TestLoginWithValidationPassWrongJSONFormat(t *testing.T) { postR.Use(gh.MiddlewareValidate) for _, v := range samples { - t.Log(v.inputJSON) req := httptest.NewRequest("POST", "/api/v1/users/login", bytes.NewBufferString(v.inputJSON)) r.ServeHTTP(w, req) - assert.Equal(t, w.Code, v.statusCode) - if v.statusCode == 200 { - assert.NotEqual(t, w.Body.String(), "") - } - - if v.statusCode == 422 && v.errorMessage != "" { - responseMap := make(map[string]interface{}) - err = json.Unmarshal([]byte(w.Body.String()), &responseMap) - if err != nil { - t.Errorf("Cannot convert to json: %v", err) - } - assert.Equal(t, responseMap["error"], v.errorMessage) - } } } \ No newline at end of file From fd8fa3c246906c2ce5cf5c70e41826855b73092c Mon Sep 17 00:00:00 2001 From: Shahriyar Rzayev Date: Thu, 4 Jun 2020 18:34:49 +0400 Subject: [PATCH 29/30] fixing some unhandled exceptions --- .../golang_rest_api/controllers/middleware.go | 2 + .../Golang_Gorilla/golang_rest_api/main.go | 5 ++- .../golang_rest_api/models/users.go | 4 +- .../controllertests/login_controller_test.go | 43 +++++++++++++++++++ 4 files changed, 50 insertions(+), 4 deletions(-) diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/middleware.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/middleware.go index db9664c..28a1adc 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/middleware.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/controllers/middleware.go @@ -84,6 +84,8 @@ func (g *GenHandler) MiddlewareValidateLogin(next http.Handler, w http.ResponseW utils.Respond(w, &GenericError{Message: err.Error()}) return } + g.Users.l.Println(login) + // validate the user errs := g.Users.v.Validate(login) if len(errs) != 0 { diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/main.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/main.go index e4a4b8f..6ef072a 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/main.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/main.go @@ -28,8 +28,9 @@ func main() { must(err) defer services.Close() - //services.DestructiveReset() - //services.AutoMigrate() + services.DestructiveReset() + services.AutoMigrateUser() + services.AutoMigrateTodo() l := log.New(os.Stdout, "users-api -> ", log.LstdFlags) v := models.NewValidation() diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/users.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/users.go index c203565..0f77bf0 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/users.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/models/users.go @@ -17,8 +17,8 @@ type User struct { } type Login struct { - Email string `json:"email" validation:"required,email"` - Password string `json:"password" validation:"required,min=5,max=15"` + Email string `json:"email" validate:"required,email"` + Password string `json:"password" validate:"required,min=5,max=15"` } // UserDB interface for holding all direct database related actions diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/controllertests/login_controller_test.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/controllertests/login_controller_test.go index a9d4c6c..ed94b2d 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/controllertests/login_controller_test.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/controllertests/login_controller_test.go @@ -52,6 +52,49 @@ func TestLoginWithValidationPassWrongJSONFormat(t *testing.T) { postR.HandleFunc("/users/login", ah.Login) postR.Use(gh.MiddlewareValidate) + for _, v := range samples { + req := httptest.NewRequest("POST", "/api/v1/users/login", bytes.NewBufferString(v.inputJSON)) + r.ServeHTTP(w, req) + assert.Equal(t, w.Code, v.statusCode) + } +} + +func TestLoginWithValidationEmptyInvalidEmail(t *testing.T) { + err := refreshUserTable() + if err != nil { + t.Fatal(err) + } + + _, err = seedOneUser() + if err != nil { + t.Fatal(err) + } + + samples := []struct { + inputJSON string + statusCode int + }{ + { + // wrong email format + inputJSON: `{"email": "rzayevsehriyargmailcom", "password": "12345"}`, + statusCode: 400, + }, + { + // empty email address + inputJSON: `{"email": "", "password": 12345"}`, + statusCode: 400, + }, + + } + + w := httptest.NewRecorder() + r := mux.NewRouter() + r.Use(controllers.CommonMiddleware) + var apiV1 = r.PathPrefix("/api/v1/").Subrouter() + postR := apiV1.Methods("POST").Subrouter() + postR.HandleFunc("/users/login", ah.Login) + postR.Use(gh.MiddlewareValidate) + for _, v := range samples { req := httptest.NewRequest("POST", "/api/v1/users/login", bytes.NewBufferString(v.inputJSON)) r.ServeHTTP(w, req) From e78772b9acec912aa5a277395470554b54aedef3 Mon Sep 17 00:00:00 2001 From: Shahriyar Rzayev Date: Thu, 4 Jun 2020 21:56:25 +0400 Subject: [PATCH 30/30] Adding few changes --- .../golang_rest_api/tests/modeltests/model_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/model_test.go b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/model_test.go index 375566b..b19039d 100644 --- a/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/model_test.go +++ b/Project1_Simple_REST_API/Golang_Gorilla/golang_rest_api/tests/modeltests/model_test.go @@ -54,7 +54,7 @@ func refreshUserTable() error { if err != nil { return err } - log.Printf("Successfully refreshed table") + log.Printf("Successfully refreshed user table") return nil } @@ -68,7 +68,7 @@ func refreshTodoTable() error { if err != nil { return err } - log.Printf("Successfully refreshed table") + log.Printf("Successfully refreshed todo table") return nil }