From 392e7e1af313f9ae0cf29fee523cb4b705b07192 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernd-Ren=C3=A9=20Predota?= Date: Wed, 4 Aug 2021 15:44:18 +0200 Subject: [PATCH] bleve intergration ongoing --- api/v1/tokens/tokens.go | 2 +- api/v1/users/route.go | 2 +- api/v1/users/users.go | 129 +++++++++++++++++++- api/v1/users/users_test.go | 48 ++++++++ cmd/add/admin/admin.go | 2 +- cmd/server/server.go | 28 ++--- go.mod | 3 + go.sum | 92 +++++++++++++++ indexes/indexes.go | 62 ++++++++++ lib/apperrors/apperrors.go | 168 +++++++++++++++++++++++++++ lib/apperrors/constants.go | 41 +++++++ lib/authentication/authentication.go | 5 +- lib/response/response.go | 45 ++++++- lib/response/response_test.go | 13 ++- main.go | 3 +- repositories/token_test.go | 37 +++--- repositories/user.go | 46 ++++++-- repositories/user_test.go | 41 ++++--- services/token_test.go | 35 +++--- services/user.go | 27 +++-- services/user_test.go | 37 +++--- 21 files changed, 745 insertions(+), 121 deletions(-) create mode 100644 api/v1/users/users_test.go create mode 100644 indexes/indexes.go create mode 100644 lib/apperrors/apperrors.go create mode 100644 lib/apperrors/constants.go diff --git a/api/v1/tokens/tokens.go b/api/v1/tokens/tokens.go index 50cf6be..1a7e5ee 100644 --- a/api/v1/tokens/tokens.go +++ b/api/v1/tokens/tokens.go @@ -21,7 +21,7 @@ func Create(c *gin.Context) { // } tokenRepo := repositories.NewTokenRepository(db, "01F5FSJXDHWT4HK93B9NB8V5G4", "test", cc) tokenService := services.NewTokenService(tokenRepo) - userID, _ := common.StringToGUID("01F5G1W6WJCKQ4PPRS36RYGQ08") + userID, _ := common.StringToGUID("01F67CNK67MVHFHPK73R7RR0A2") t := &models.Token{ ExpiresAt: time.Now().Add(24 * time.Hour), UserID: userID, diff --git a/api/v1/users/route.go b/api/v1/users/route.go index af5932a..f6108ca 100644 --- a/api/v1/users/route.go +++ b/api/v1/users/route.go @@ -9,7 +9,7 @@ import ( func ApplyRoutes(r *gin.RouterGroup) *gin.RouterGroup { users := r.Group("/users") { - // actions.POST("", middlewares.Authorized, CreateActions) + users.POST("", authentication.Authorized, Create) users.GET("/:username", authentication.Authorized, ReadByID) users.GET("", authentication.Authorized, ReadAll) // actions.GET("/:id/logs", middlewares.Authorized, actionlogs.ReadActionLogs) diff --git a/api/v1/users/users.go b/api/v1/users/users.go index 57f05c5..7e1d07d 100644 --- a/api/v1/users/users.go +++ b/api/v1/users/users.go @@ -1,11 +1,21 @@ package users import ( + "fmt" + "net/http" + "strconv" + + "git.devices.local/mawas/golang-api-skeleton/lib/apperrors" "git.devices.local/mawas/golang-api-skeleton/lib/cache" + "git.devices.local/mawas/golang-api-skeleton/lib/common" "git.devices.local/mawas/golang-api-skeleton/lib/response" + "git.devices.local/mawas/golang-api-skeleton/models" "git.devices.local/mawas/golang-api-skeleton/repositories" "git.devices.local/mawas/golang-api-skeleton/services" + "github.com/blevesearch/bleve" + "github.com/blevesearch/bleve/v2/search" "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" "gorm.io/gorm" ) @@ -23,23 +33,134 @@ func prepare(c *gin.Context) (response.Envelope, *services.UserService) { return resp, userService } +// Create handler function for POST /api/v1/users +func Create(c *gin.Context) { + var requestBody []*models.User + resp, userService := prepare(c) + defer resp.Recovery(c) + if err := c.ShouldBindBodyWith(&requestBody, binding.JSON); err != nil { + c.AbortWithStatusJSON(resp.HTTPError(apperrors.NewError( + apperrors.ValidationFailed, + err.Error(), + ))) + return + } + users, err := userService.Create(requestBody) + if err != nil { + c.AbortWithStatusJSON(resp.HTTPError(err)) + return + } + index := c.MustGet("index").(bleve.Index) + for i := range users { + index.Index(users[i].ID.String(), users[i]) + } + c.JSON(http.StatusOK, resp.SetSuccess(users)) +} + +// ReadByID handler function for path GET /api/v1/users/:id func ReadByID(c *gin.Context) { resp, userService := prepare(c) username := c.Param("username") + // index, err := bleve.Open("users.bleve") + // if err != nil { + // fmt.Println("error", err.Error()) + // } + index := c.MustGet("index").(bleve.Index) + raw, err := index.Document(username) + if err != nil { + fmt.Println("error", err.Error()) + } + fmt.Println("<>>>>>>", string(raw.Fields[0].Value())) + query := bleve.NewMatchQuery("jdoe") + search := bleve.NewSearchRequest(query) + searchResults, err := index.Search(search) + if err != nil { + fmt.Println("error", err.Error()) + } + fmt.Println(searchResults) user, err := userService.ReadByID(username) if err != nil { - c.AbortWithStatusJSON(500, resp.AppendError(err)) + c.AbortWithStatusJSON(resp.HTTPError(err)) return } - c.JSON(200, resp.SetSuccess(user)) + c.JSON(http.StatusOK, resp.SetSuccess(user)) } +// ReadALL handler function for path GET /api/v1/users func ReadAll(c *gin.Context) { + filter := c.Query("filter") + page, _ := strconv.Atoi(c.Query("page")) + limit, _ := strconv.Atoi(c.Query("limit")) + if page > 0 { + page += -1 + } + fmt.Println(page * limit) resp, userService := prepare(c) + // filter := "tokens:01F5G2F9B2CZZVV67MVDMJ5SBZ" + // filter := "firstname:jane" + // filter := "*mustermann*" + index := c.MustGet("index").(bleve.Index) + // query := bleve.NewMatchQuery(filter) + var err error + if filter != "" { + query := bleve.NewQueryStringQuery(filter) + searchRequest := bleve.NewSearchRequest(query) + s := search.SortOrder{ + &search.SortField{ + Field: "Username", + Missing: search.SortFieldMissingFirst, + }, + &search.SortDocID{}, + } + searchRequest.SortByCustom(s) + // search.Fields = []string{"Username", "Firstname", "Lastname", "Email", "Tokens"} + searchRequest.SortBy([]string{"_id"}) + searchRequest.IncludeLocations = true + searchResults, err := index.Search(searchRequest) + if err != nil { + fmt.Println("error", err.Error()) + } + ids := make([]common.GUID, len(searchResults.Hits)) + for i := range searchResults.Hits { + ids[i], _ = common.StringToGUID(searchResults.Hits[i].ID) + } + debugFilterPrint(searchResults) + users, err := userService.ReadSelection(ids) + if err != nil { + c.AbortWithStatusJSON(resp.HTTPError(err)) + return + } + c.JSON(http.StatusOK, resp.SetSuccess(users)) + return + } + // fmt.Println("Limit:", limit, "Page:", page) + // search := bleve.NewSearchRequest(query) + // if limit > 0 { + // search = bleve.NewSearchRequestOptions(query, limit, page*limit, true) + // } + users, err := userService.ReadAll() if err != nil { - c.AbortWithStatusJSON(500, resp.AppendError(err)) + c.AbortWithStatusJSON(resp.HTTPError(err)) return } - c.JSON(200, resp.SetSuccess(users)) + c.JSON(http.StatusOK, resp.SetSuccess(users)) +} + +func debugFilterPrint(searchResults *bleve.SearchResult) { + fmt.Println("Total:", searchResults.Total, len(searchResults.Hits)) + for _, hit := range searchResults.Hits { + fmt.Println(hit.Fields) + for k, v := range hit.Fields { + fmt.Printf("Field %v = %v\n", k, v) + } + for fieldName, fieldMap := range hit.Locations { + for termName, locations := range fieldMap { + for _, location := range locations { + // may be usefull to report back for highlighting in filter_info or so + fmt.Printf(" Field %s has term %s from %d to %d (Pos %d?)\n", fieldName, termName, location.Start, location.End, location.Pos) + } + } + } + } } diff --git a/api/v1/users/users_test.go b/api/v1/users/users_test.go new file mode 100644 index 0000000..7dc8772 --- /dev/null +++ b/api/v1/users/users_test.go @@ -0,0 +1,48 @@ +package users + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/gin-gonic/gin" +) + +func usersMock(t *testing.T) *gin.Engine { + gin.SetMode(gin.TestMode) + app := gin.Default() + // create token + return app +} + +func TestUsersEndpoint(t *testing.T) { + app := usersMock(t) + api := app.Group("/api/v1") + ApplyRoutes(api) + request, err := http.NewRequest("GET", "/api/v1/users", nil) + if err != nil { + t.Error(err) + } + // Unauthorized + response := httptest.NewRecorder() + app.ServeHTTP(response, request) + if response.Result().StatusCode != http.StatusUnauthorized { + t.Errorf("api users - http status code must be \"%d\" (but is \"%d\") because of unauthorized request", http.StatusUnauthorized, response.Result().StatusCode) + } + body := response.Body.String() + var j map[string]interface{} + if err := json.Unmarshal([]byte(body), &j); err != nil { + t.Error(err) + } + if _, exists := j["success"]; !exists { + t.Error("api users - success flag needs always be given") + } else if success, ok := j["success"].(bool); !ok { + t.Error("api users - success flag needs to be boolean") + } else if success { + t.Error("api users - success flag needs to be false because of unauthorized request") + } + // Create user + // request.Header.Set("Authorization", "Bearer "+token) + // t.Error(response) +} diff --git a/cmd/add/admin/admin.go b/cmd/add/admin/admin.go index ba47292..753c897 100644 --- a/cmd/add/admin/admin.go +++ b/cmd/add/admin/admin.go @@ -68,6 +68,6 @@ func createAdminUser(appName, cfgFile string, flagCfg map[string]interface{}, us userService := services.NewUserService(userRepo) adminUser := services.NewUser(username, firstname, lastname, email) adminUser.Active = true - _, err = userService.Create(&adminUser) + _, err = userService.Create([]*models.User{&adminUser}) return err } diff --git a/cmd/server/server.go b/cmd/server/server.go index 18110f0..dc236ae 100644 --- a/cmd/server/server.go +++ b/cmd/server/server.go @@ -4,6 +4,7 @@ import ( "fmt" "git.devices.local/mawas/golang-api-skeleton/api" + "git.devices.local/mawas/golang-api-skeleton/indexes" "git.devices.local/mawas/golang-api-skeleton/lib/cache" "git.devices.local/mawas/golang-api-skeleton/lib/common" "git.devices.local/mawas/golang-api-skeleton/lib/config" @@ -69,6 +70,13 @@ func startServer(version string, appName string, cfgFile string, flagCfg map[str if err := c.Set("user:test", "01F5FSJXDHWT4HK93B9NB8V5G4"); err != nil { // username -> userID mapping return fmt.Errorf("cache:%v", err) } + // FIXME find out where todo database migration + if err := db.AutoMigrate(&models.Token{}); err != nil { + return fmt.Errorf("database:%v", err) + } + if err := db.AutoMigrate(&models.User{}); err != nil { + return fmt.Errorf("database:%v", err) + } // Prefill cache // TODO also add deleted users to cache for old references // users @@ -76,7 +84,7 @@ func startServer(version string, appName string, cfgFile string, flagCfg map[str userService := services.NewUserService(userRepo) userData, err := userService.ReadAll() if err != nil { - return fmt.Errorf("cache:%v", err) + return fmt.Errorf("cache:%v", err.Error()) } for i := range userData { if err := c.Set("user:"+userData[i].ID.String(), userData[i].Username); err != nil { // userID -> username mapping @@ -98,18 +106,16 @@ func startServer(version string, appName string, cfgFile string, flagCfg map[str return fmt.Errorf("cache:%v", err) } } + index, err := indexes.Init(db) + if err != nil { + return fmt.Errorf("index:%v", err) + } + app.Use(inject("index", index)) app.Use(inject("cache", c)) app.Use(inject("db", db)) app.Use(middlewares.RequestID()) app.Use(middlewares.Authentication()) app.Use(middlewares.VersionHeader(version)) - // FIXME find out where todo database migration - if err := db.AutoMigrate(&models.Token{}); err != nil { - return fmt.Errorf("database:%v", err) - } - if err := db.AutoMigrate(&models.User{}); err != nil { - return fmt.Errorf("database:%v", err) - } api.ApplyRoutes(app) if err := app.Run(fmt.Sprintf("%s:%s", viper.GetString("application.listenaddress"), viper.GetString("application.port"))); err != nil { return fmt.Errorf("api:%v", err) @@ -117,12 +123,6 @@ func startServer(version string, appName string, cfgFile string, flagCfg map[str return nil } -// func ping(c *gin.Context) { -// c.JSON(200, gin.H{ -// "message": "pong", -// }) -// } - // Inject injects database to gin context func inject(key string, value interface{}) gin.HandlerFunc { return func(c *gin.Context) { diff --git a/go.mod b/go.mod index 3a3de7b..b9f67d6 100644 --- a/go.mod +++ b/go.mod @@ -4,9 +4,12 @@ go 1.15 require ( github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef // indirect + github.com/blevesearch/bleve v1.0.14 // indirect + github.com/blevesearch/bleve/v2 v2.0.3 // indirect github.com/dgraph-io/badger/v2 v2.2007.2 github.com/fsnotify/fsnotify v1.4.9 // indirect github.com/gin-gonic/gin v1.7.1 // indirect + github.com/go-errors/errors v1.4.0 // indirect github.com/go-ozzo/ozzo-validation v3.6.0+incompatible github.com/go-ozzo/ozzo-validation/v4 v4.3.0 github.com/magiconair/properties v1.8.4 // indirect diff --git a/go.sum b/go.sum index 669c69e..94767ef 100644 --- a/go.sum +++ b/go.sum @@ -17,6 +17,8 @@ github.com/DataDog/zstd v1.4.1 h1:3oxKN3wbHibqx897utPC2LTQU4J+IHWWJO+glkAkpFM= github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/RoaringBitmap/roaring v0.4.23 h1:gpyfd12QohbqhFO4NVDUdoPOCXsyahYRQhINmlHxKeo= +github.com/RoaringBitmap/roaring v0.4.23/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06Mq5mKs52e1TwOo= 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/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= @@ -30,6 +32,48 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/blevesearch/bleve v1.0.14 h1:Q8r+fHTt35jtGXJUM0ULwM3Tzg+MRfyai4ZkWDy2xO4= +github.com/blevesearch/bleve v1.0.14/go.mod h1:e/LJTr+E7EaoVdkQZTfoz7dt4KoDNvDbLb8MSKuNTLQ= +github.com/blevesearch/bleve/v2 v2.0.3 h1:mDrwrsRIA4PDYkfUNjoh5zGECvquuJIA3MJU5ivaO8E= +github.com/blevesearch/bleve/v2 v2.0.3/go.mod h1:ip+4iafiEq2gCY5rJXe87bT6LkF/OJMCjQEYIfTBfW8= +github.com/blevesearch/bleve_index_api v1.0.0 h1:Ds3XeuTxjXCkG6pgIwWDRyooJKNIuOKemnN0N0IkhTU= +github.com/blevesearch/bleve_index_api v1.0.0/go.mod h1:fiwKS0xLEm+gBRgv5mumf0dhgFr2mDgZah1pqv1c1M4= +github.com/blevesearch/blevex v1.0.0/go.mod h1:2rNVqoG2BZI8t1/P1awgTKnGlx5MP9ZbtEciQaNhswc= +github.com/blevesearch/cld2 v0.0.0-20200327141045-8b5f551d37f5/go.mod h1:PN0QNTLs9+j1bKy3d/GB/59wsNBFC4sWLWG3k69lWbc= +github.com/blevesearch/go-porterstemmer v1.0.3 h1:GtmsqID0aZdCSNiY8SkuPJ12pD4jI+DdXTAn4YRcHCo= +github.com/blevesearch/go-porterstemmer v1.0.3/go.mod h1:angGc5Ht+k2xhJdZi511LtmxuEf0OVpvUUNrwmM1P7M= +github.com/blevesearch/mmap-go v1.0.2 h1:JtMHb+FgQCTTYIhtMvimw15dJwu1Y5lrZDMOFXVWPk0= +github.com/blevesearch/mmap-go v1.0.2/go.mod h1:ol2qBqYaOUsGdm7aRMRrYGgPvnwLe6Y+7LMvAB5IbSA= +github.com/blevesearch/scorch_segment_api/v2 v2.0.1 h1:fd+hPtZ8GsbqPK1HslGp7Vhoik4arZteA/IsCEgOisw= +github.com/blevesearch/scorch_segment_api/v2 v2.0.1/go.mod h1:lq7yK2jQy1yQjtjTfU931aVqz7pYxEudHaDwOt1tXfU= +github.com/blevesearch/segment v0.9.0 h1:5lG7yBCx98or7gK2cHMKPukPZ/31Kag7nONpoBt22Ac= +github.com/blevesearch/segment v0.9.0/go.mod h1:9PfHYUdQCgHktBgvtUOF4x+pc4/l8rdH0u5spnW85UQ= +github.com/blevesearch/snowballstem v0.9.0 h1:lMQ189YspGP6sXvZQ4WZ+MLawfV8wOmPoD/iWeNXm8s= +github.com/blevesearch/snowballstem v0.9.0/go.mod h1:PivSj3JMc8WuaFkTSRDW2SlrulNWPl4ABg1tC/hlgLs= +github.com/blevesearch/upsidedown_store_api v1.0.1 h1:1SYRwyoFLwG3sj0ed89RLtM15amfX2pXlYbFOnF8zNU= +github.com/blevesearch/upsidedown_store_api v1.0.1/go.mod h1:MQDVGpHZrpe3Uy26zJBf/a8h0FZY6xJbthIMm8myH2Q= +github.com/blevesearch/vellum v1.0.3 h1:U86G41A7CtXNzzpIJHM8lSTUqz1Mp8U870TkcdCzZc8= +github.com/blevesearch/vellum v1.0.3/go.mod h1:2u5ax02KeDuNWu4/C+hVQMD6uLN4txH1JbtpaDNLJRo= +github.com/blevesearch/zap/v11 v11.0.14 h1:IrDAvtlzDylh6H2QCmS0OGcN9Hpf6mISJlfKjcwJs7k= +github.com/blevesearch/zap/v11 v11.0.14/go.mod h1:MUEZh6VHGXv1PKx3WnCbdP404LGG2IZVa/L66pyFwnY= +github.com/blevesearch/zap/v12 v12.0.14 h1:2o9iRtl1xaRjsJ1xcqTyLX414qPAwykHNV7wNVmbp3w= +github.com/blevesearch/zap/v12 v12.0.14/go.mod h1:rOnuZOiMKPQj18AEKEHJxuI14236tTQ1ZJz4PAnWlUg= +github.com/blevesearch/zap/v13 v13.0.6 h1:r+VNSVImi9cBhTNNR+Kfl5uiGy8kIbb0JMz/h8r6+O4= +github.com/blevesearch/zap/v13 v13.0.6/go.mod h1:L89gsjdRKGyGrRN6nCpIScCvvkyxvmeDCwZRcjjPCrw= +github.com/blevesearch/zap/v14 v14.0.5 h1:NdcT+81Nvmp2zL+NhwSvGSLh7xNgGL8QRVZ67njR0NU= +github.com/blevesearch/zap/v14 v14.0.5/go.mod h1:bWe8S7tRrSBTIaZ6cLRbgNH4TUDaC9LZSpRGs85AsGY= +github.com/blevesearch/zap/v15 v15.0.3 h1:Ylj8Oe+mo0P25tr9iLPp33lN6d4qcztGjaIsP51UxaY= +github.com/blevesearch/zap/v15 v15.0.3/go.mod h1:iuwQrImsh1WjWJ0Ue2kBqY83a0rFtJTqfa9fp1rbVVU= +github.com/blevesearch/zapx/v11 v11.2.0 h1:GBkCJYsyj3eIU4+aiLPxoMz1PYvDbQZl/oXHIBZIP60= +github.com/blevesearch/zapx/v11 v11.2.0/go.mod h1:gN/a0alGw1FZt/YGTo1G6Z6XpDkeOfujX5exY9sCQQM= +github.com/blevesearch/zapx/v12 v12.2.0 h1:dyRcSoZVO1jktL4UpGkCEF1AYa3xhKPirh4/N+Va+Ww= +github.com/blevesearch/zapx/v12 v12.2.0/go.mod h1:fdjwvCwWWwJW/EYTYGtAp3gBA0geCYGLcVTtJEZnY6A= +github.com/blevesearch/zapx/v13 v13.2.0 h1:mUqbaqQABp8nBE4t4q2qMyHCCq4sykoV8r7aJk4ih3s= +github.com/blevesearch/zapx/v13 v13.2.0/go.mod h1:o5rAy/lRS5JpAbITdrOHBS/TugWYbkcYZTz6VfEinAQ= +github.com/blevesearch/zapx/v14 v14.2.0 h1:UsfRqvM9RJxKNKrkR1U7aYc1cv9MWx719fsAjbF6joI= +github.com/blevesearch/zapx/v14 v14.2.0/go.mod h1:GNgZusc1p4ot040cBQMRGEZobvwjCquiEKYh1xLFK9g= +github.com/blevesearch/zapx/v15 v15.2.0 h1:ZpibwcrrOaeslkOw3sJ7npP7KDgRHI/DkACjKTqFwyM= +github.com/blevesearch/zapx/v15 v15.2.0/go.mod h1:MmQceLpWfME4n1WrBFIwplhWmaQbQqLQARpaKUEOs/A= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= @@ -44,9 +88,16 @@ github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/couchbase/ghistogram v0.1.0/go.mod h1:s1Jhy76zqfEecpNWJfWUiKZookAFaiGOEoyzgHt9i7k= +github.com/couchbase/moss v0.1.0/go.mod h1:9MaHIaRuy9pvLPUJxB8sh8OrLfyDczECVL37grCIubs= +github.com/couchbase/vellum v1.0.2 h1:BrbP0NKiyDdndMPec8Jjhy0U47CZ0Lgx3xUC2r9rZqw= +github.com/couchbase/vellum v1.0.2/go.mod h1:FcwrEivFpNi24R3jLOs3n+fs5RnuQnQqCLBJ1uAg1W4= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/cznic/b v0.0.0-20181122101859-a26611c4d92d/go.mod h1:URriBxXwVq5ijiJ12C7iIZqlA69nTlI+LgI6/pwftG8= +github.com/cznic/mathutil v0.0.0-20181122101859-297441e03548/go.mod h1:e6NPNENfs9mPDVNRekM7lKScauxd5kXTr1Mfyig6TDM= +github.com/cznic/strutil v0.0.0-20181122101858-275e90344537/go.mod h1:AHHPPPXTw0h6pVabbcbyGRK1DckRn7r/STdZEeIDzZc= 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= @@ -62,6 +113,9 @@ github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUn github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64= +github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg= +github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= @@ -71,6 +125,11 @@ github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.7.1 h1:qC89GU3p8TvKWMAVhEpmpB2CIb1hnqt2UdKZaP93mS8= github.com/gin-gonic/gin v1.7.1/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= +github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2 h1:Ujru1hufTHVb++eG6OuNDKMxZnGIvF6o/u8q/8h2+I4= +github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= +github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= +github.com/go-errors/errors v1.4.0 h1:2OA7MFw38+e9na72T1xgkomPb6GzZzzxvJ5U630FoRM= +github.com/go-errors/errors v1.4.0/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= 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= @@ -106,6 +165,7 @@ github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -121,6 +181,7 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 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= @@ -146,6 +207,8 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ikawaha/kagome.ipadic v1.1.2/go.mod h1:DPSBbU0czaJhAb/5uKQZHMc9MTVRpDugJfX+HddPHHg= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0= @@ -204,6 +267,7 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.1 h1:g39TucaRWyV3dwDO++eEc6qf8TVIQ/Da48WmqjZ3i7E= github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+nNuzMJQ= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= @@ -214,6 +278,7 @@ github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfV github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kljensen/snowball v0.6.0/go.mod h1:27N7E8fVU5H68RlUmnWwZCfxgt4POBJfENGMvNRhldw= 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/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= @@ -266,16 +331,24 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg= +github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM= +github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/oklog/ulid/v2 v2.0.2 h1:r4fFzBm+bv0wNKNh5eXTwU7i85y5x+uwkxCUTNVQqLc= github.com/oklog/ulid/v2 v2.0.2/go.mod h1:mtBL0Qe/0HAx6/a4Z30qxVIAL1eQDweXq5lxOEiwQ68= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM= github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= +github.com/philhofer/fwd v1.0.0 h1:UbZqGr5Y38ApvM/V/jEljVxwocdweyH+vmYvRPBnbqQ= +github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -292,6 +365,8 @@ github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8 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/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= @@ -338,6 +413,8 @@ github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DM github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk= github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/steveyen/gtreap v0.1.0 h1:CjhzTa274PyJLJuMZwIzCO1PfC00oRa8d1Kc78bFXJM= +github.com/steveyen/gtreap v0.1.0/go.mod h1:kl/5J7XbrOmlIbYIXdRHDDE5QxHqpk0cmkT7Z4dM9/Y= 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= @@ -348,16 +425,25 @@ github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 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/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= +github.com/tebeka/snowball v0.4.2/go.mod h1:4IfL14h1lvwZcp1sfXuuc7/7yCsvVffTWxWxCLfFpYg= +github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c/go.mod h1:ahpPrc7HpcfEWDQRZEmnXMzHY03mLDYMCxeDzy46i+8= +github.com/tinylib/msgp v1.1.0 h1:9fQd+ICuRIu/ue4vxJZu6/LzxN0HwMds2nq/0cFvxHU= +github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +github.com/willf/bitset v1.1.10 h1:NotGKqX0KwQ72NUzqrjZq5ipPNDQex9lo3WpaS8L2sc= +github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= 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/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0= +go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= @@ -401,6 +487,7 @@ golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKG golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= 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-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/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-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -426,10 +513,12 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 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-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/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-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181221143128-b4a75ba826a6/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-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -446,6 +535,7 @@ golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -510,6 +600,7 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= @@ -517,6 +608,7 @@ gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 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= diff --git a/indexes/indexes.go b/indexes/indexes.go new file mode 100644 index 0000000..c44ea37 --- /dev/null +++ b/indexes/indexes.go @@ -0,0 +1,62 @@ +package indexes + +import ( + "fmt" + + "git.devices.local/mawas/golang-api-skeleton/lib/common" + "git.devices.local/mawas/golang-api-skeleton/repositories" + "git.devices.local/mawas/golang-api-skeleton/services" + "github.com/blevesearch/bleve" + "gorm.io/gorm" +) + +func Init(db *gorm.DB) (bleve.Index, error) { + tokenMapping := bleve.NewDocumentMapping() + tokenMapping.AddFieldMappingsAt("Token", bleve.NewTextFieldMapping()) + userMapping := bleve.NewDocumentMapping() + userMapping.AddSubDocumentMapping("Tokens", tokenMapping) + userMapping.AddFieldMappingsAt("Username", bleve.NewTextFieldMapping()) + userMapping.AddFieldMappingsAt("Firstname", bleve.NewTextFieldMapping()) + userMapping.AddFieldMappingsAt("Lastname", bleve.NewTextFieldMapping()) + userMapping.AddFieldMappingsAt("Email", bleve.NewTextFieldMapping()) + userMapping.AddFieldMappingsAt("Active", bleve.NewTextFieldMapping()) + + // indexName := "users.bleve" + mapping := bleve.NewIndexMapping() + mapping.AddDocumentMapping("Thread", userMapping) + + // ???? mapping.TypeField = "Username" + + // index, err := bleve.New(indexName, mapping) + index, err := bleve.NewMemOnly(mapping) + if err != nil { + return index, err + } + userRepo := repositories.NewUserRepository(db, common.CLIUserID, common.CLIUsername, nil) + userService := services.NewUserService(userRepo) + users, _ := userService.ReadAll() + for i := range users { + id := users[i].ID + // doc, err := json.Marshal(users[i]) + // if err != nil { + // return index, err + // } + // fmt.Println(id, string(doc)) + err = index.Index(id.String(), users[i]) + if err != nil { + return index, err + } + // err = index.SetInternal([]byte(id), doc) + // if err != nil { + // return index, err + // } + } + query := bleve.NewMatchQuery("Mustermann") + search := bleve.NewSearchRequest(query) + searchResults, err := index.Search(search) + if err != nil { + fmt.Println("error", err.Error()) + } + fmt.Println(searchResults) + return index, nil +} diff --git a/lib/apperrors/apperrors.go b/lib/apperrors/apperrors.go new file mode 100644 index 0000000..35a4437 --- /dev/null +++ b/lib/apperrors/apperrors.go @@ -0,0 +1,168 @@ +package apperrors + +import ( + "net/http" + "strings" +) + +type APPError interface { + GetType() ErrorKey + GetHTTPStatus() int + GetDetail() string + SetDetail(string) + Error() string +} + +// Error response structure +type Error struct { + Code int `json:"code"` + Type ErrorKey `json:"-"` + Message string `json:"message"` + Detail string `json:"detail"` + HTTPStatus int `json:"-"` + ObjectID uint64 `json:"-"` + ObjectName string `json:"-"` +} + +func (err Error) GetType() ErrorKey { + return err.Type +} + +func (err Error) GetDetail() string { + return err.Detail +} + +func (err Error) SetDetail(detail string) { + err.Detail = detail +} + +func (err Error) GetHTTPStatus() int { + return customErrors[err.Type].HTTPStatus +} + +func (err Error) Error() string { + return err.Detail +} + +func NewError(errType ErrorKey, detail string) (err APPError) { + switch errType { + case DBError, InvalidDBQuery: + detail = humanReadableDatabaseError(detail) + case ValidationFailed: + detail = humanReadableMarshalError(detail) + } + return Error{ + Type: errType, + Code: customErrors[errType].Code, + Message: customErrors[errType].Message, + Detail: detail, + HTTPStatus: customErrors[errType].HTTPStatus, + } +} + +// customErrors object contains self defined error codes and messages +var customErrors = map[ErrorKey]Error{ + ItemNotFound: { + Code: 1000, + Message: MessageItemNotFound, + HTTPStatus: http.StatusNotFound, + }, + InvalidID: { + Code: 1001, + Message: MessageInvalidID, + HTTPStatus: http.StatusBadRequest, + }, + ValidationFailed: { + Code: 1002, + Message: MessageValidationFailed, + HTTPStatus: http.StatusBadRequest, + }, + PathNotFound: { + Code: 4000, + Message: MessagePathNotFound, + HTTPStatus: http.StatusNotFound, + }, + MalformedQueryParameter: { + Code: 4001, + Message: MessageMalformedQueryParameter, + HTTPStatus: http.StatusBadRequest, + }, + MalformedFilterString: { + Code: 4002, + Message: MessageMalformedFilterString, + HTTPStatus: http.StatusBadRequest, + }, + InvalidPagination: { + Code: 4003, + Message: MessageInvalidPagination, + HTTPStatus: http.StatusBadRequest, + }, + UnknownError: { + Code: 5000, + Message: MessageUnknownError, + HTTPStatus: http.StatusInternalServerError, + }, + DBError: { + Code: 5002, + Message: MessageDBError, + HTTPStatus: http.StatusInternalServerError, + }, + InvalidDBQuery: { + Code: 5003, + Message: MessageInvalidDBQuery, + HTTPStatus: http.StatusInternalServerError, + }, + EmptyDBResponse: { + Code: 5004, + Message: MessageEmptyDBResponse, + HTTPStatus: http.StatusNotFound, + }, + Unauthorized: { + Code: 6000, + Message: MessageUnauthorized, + HTTPStatus: http.StatusUnauthorized, + }, + Forbidden: { + Code: 6001, + Message: MessageForbidden, + HTTPStatus: http.StatusForbidden, + }, + ReportBug: { + Code: 9999, + Message: MessageReportBug, + HTTPStatus: http.StatusInternalServerError, + }, +} + +// humanReadableMarshalError translates some cryptic error messages +func humanReadableMarshalError(detail string) string { + switch { + case strings.Contains(detail, "decode slice: expect [ or n, but found {"): + return "payload must be a list" + case strings.Contains(detail, "json: cannot unmarshal object into Go value of type ["): + return "payload must be a list" + case strings.Contains(detail, "strconv.ParseUint: parsing "): + return "id must be numeric" + case strings.Contains(detail, "json: cannot unmarshal array into Go value of type"), + strings.HasPrefix(detail, "readObjectStart: expect { or n, but found ["): + return "payload must be an object" + default: + return detail + } +} + +// humanReadableDatabaseError translates some cryptic error messages +func humanReadableDatabaseError(detail string) string { + switch { + case strings.Contains(detail, "Cannot insert duplicate key row in object 'dbo.users' with unique index 'username'."): + return "username already in use, no duplicate usernames allowed" + case strings.Contains(detail, "Cannot insert duplicate key row in object 'dbo.roles' with unique index 'rolename'."): + return "rolename already in use, no duplicate rolenames allowed" + case (strings.Contains(detail, "Duplicate entry") && strings.HasSuffix(detail, "for key 'idx_property_name_tenant_sid'")): + return "propertyname already in use, no duplicate propertynames allowed: " + detail + case strings.Contains(detail, "Cannot insert duplicate key in object 'dbo.tenants'."): + return "Tenant name or id in use, no duplicates allowed" + default: + return detail + } +} diff --git a/lib/apperrors/constants.go b/lib/apperrors/constants.go new file mode 100644 index 0000000..6acca51 --- /dev/null +++ b/lib/apperrors/constants.go @@ -0,0 +1,41 @@ +package apperrors + +type ErrorKey uint16 + +const ( + NoError ErrorKey = iota + PathNotFound + CacheError + DBError + InvalidDBQuery + EmptyDBResponse + ValidationFailed + UnknownError + InvalidID + MalformedFilterString + MalformedQueryParameter + ItemNotFound + InvalidPagination + Unauthorized + Forbidden + ReportBug +) + +// Custom error response messages +const ( + MessagePathNotFound = "Path Not Found" + MessageCacheError = "Cache Error" + MessageDBError = "Database Error" + MessageInvalidDBQuery = "Invalid Database Query" + MessageEmptyDBResponse = "Empty DB Response" + MessageValidationFailed = "JSON Body Validation Failed" + MessageUnknownError = "Unknown Error" + MessageInvalidID = "Invalid ID" + MessageMalformedFilterString = "Malformed Filter String" + MessageMalformedQueryParameter = "Malformed Query Parameter" + MessageInvalidPagination = "Invalid Pagination Parameter" + MessageItemNotFound = "Item Not Found" + MessageUnauthorized = "Unauthorized" + MessageForbidden = "Forbidden" + MessageReportBug = "Bug found, please report" +) diff --git a/lib/authentication/authentication.go b/lib/authentication/authentication.go index 24eef4a..8a74505 100644 --- a/lib/authentication/authentication.go +++ b/lib/authentication/authentication.go @@ -1,9 +1,9 @@ package authentication import ( - "errors" "fmt" + "git.devices.local/mawas/golang-api-skeleton/lib/apperrors" "git.devices.local/mawas/golang-api-skeleton/lib/response" "github.com/gin-gonic/gin" ) @@ -13,7 +13,8 @@ func Authorized(c *gin.Context) { var resp response.Envelope userID, exists := c.Get("userID") if !exists { - c.AbortWithStatusJSON(403, resp.AppendError(errors.New("unauthorized"))) + e := apperrors.NewError(apperrors.Unauthorized, "") + c.AbortWithStatusJSON(e.GetHTTPStatus(), resp.AppendError(e)) return } fmt.Println("permission check", userID) diff --git a/lib/response/response.go b/lib/response/response.go index 040fa83..05f44d5 100644 --- a/lib/response/response.go +++ b/lib/response/response.go @@ -1,11 +1,21 @@ package response +import ( + "fmt" + "net/http" + "net/http/httputil" + + "git.devices.local/mawas/golang-api-skeleton/lib/apperrors" + "github.com/gin-gonic/gin" + errs "github.com/go-errors/errors" +) + // Envelope for response objects type Envelope struct { - Success bool `json:"success"` - RequestID string `json:"request_id,omitempty"` - Warnings []string `json:"warnings,omitempty"` - Errors []string `json:"errors,omitempty"` + Success bool `json:"success"` + RequestID string `json:"request_id,omitempty"` + Warnings []string `json:"warnings,omitempty"` + Errors []apperrors.Error `json:"errors,omitempty"` // PaginationInfo *pagination.Pagination `json:"pagination_info,omitempty"` Result interface{} `json:"result,omitempty"` } @@ -14,13 +24,38 @@ type Envelope struct { func (envelope *Envelope) AppendError(err error) *Envelope { envelope.Success = false envelope.Result = nil - envelope.Errors = append(envelope.Errors, err.Error()) + envelope.Errors = append(envelope.Errors, err.(apperrors.Error)) return envelope } +// HTTPError to envelope +func (envelope *Envelope) HTTPError(err error) (int, *Envelope) { + envelope.Success = false + envelope.Result = nil + if e, ok := err.(apperrors.Error); ok { + envelope.Errors = append(envelope.Errors, e) + return e.HTTPStatus, envelope + } + e := apperrors.NewError(apperrors.UnknownError, err.Error()) + envelope.Errors = append(envelope.Errors, e.(apperrors.Error)) + return http.StatusInternalServerError, envelope +} + // SetSuccess to envelope func (envelope *Envelope) SetSuccess(result interface{}) *Envelope { envelope.Success = true envelope.Result = result return envelope } + +// Recovery returns with JSON error after panic +func (envelope *Envelope) Recovery(c *gin.Context) { + if err := recover(); err != nil { + httprequest, _ := httputil.DumpRequest(c.Request, false) + goErr := errs.Wrap(err, 3) + reset := string([]byte{27, 91, 48, 109}) + fmt.Printf("panic recovered:\n\n%s%s\n\n%s%s", httprequest, goErr.Error(), goErr.Stack(), reset) + envelope.AppendError(apperrors.NewError(apperrors.UnknownError, fmt.Sprintf("%s\n%s", goErr.Error(), goErr.Stack()))) + c.AbortWithStatusJSON(500, envelope) + } +} diff --git a/lib/response/response_test.go b/lib/response/response_test.go index 865bd71..1a92781 100644 --- a/lib/response/response_test.go +++ b/lib/response/response_test.go @@ -1,14 +1,15 @@ package response import ( - "errors" "testing" + + "git.devices.local/mawas/golang-api-skeleton/lib/apperrors" ) func TestAppendError(t *testing.T) { - const errMsg = "expected error" - envelope := Envelope{} - e := envelope.AppendError(errors.New(errMsg)) + var envelope Envelope + err := apperrors.NewError(apperrors.DBError, "expected error") + e := envelope.AppendError(err) if e.Success { t.Error("error response Success must be false") } @@ -17,8 +18,8 @@ func TestAppendError(t *testing.T) { } if len(e.Errors) != 1 { t.Error("error response Errors must be set") - } else if e.Errors[0] != errMsg { - t.Errorf("error response error message must be \"%s\" but is \"%s\"", errMsg, e.Errors[0]) + } else if e.Errors[0] != err { + t.Errorf("error response error message must be \"%s\" but is \"%s\"", err, e.Errors[0]) } } diff --git a/main.go b/main.go index 05a63d7..3b3e7e6 100644 --- a/main.go +++ b/main.go @@ -12,7 +12,7 @@ const defaultHost = "unknown" // https://towardsdatascience.com/building-restful-apis-in-golang-e3fe6e3f8f95 // https://www.voile.com/voile-straps.html -// https://yusufs.medium.com/creating-distributed-kv-database-by-implementing-raft-consensus-using-golang-d0884eef2e28 RAFT to create badgerdb sync? +// https://yusufs.medium.com/creating-distributed-kv-database-by-implementing-raft-consensus-using-golang-d0884eef2e28 RAFT to create badgerdb sync? dragonboat? // FIXME how to set unique index for username to make soft deletes possible but disallow duplicates... also how to cache a username which got delete and recerated... user ID would get overwritten, maybe pre/suffix deleted username? // TODO add command for db migrate? no auto migrate? @@ -25,6 +25,7 @@ const defaultHost = "unknown" // TODO add logger to gin // TODO make explicit getDBConnection function // TODO restructure flags e.g. listenaddress and port to server +// FIXME https://medium.com/telnet/why-should-you-start-reading-documentation-from-limitations-section-6690c08e5d1a#.em5kfkezd - too many SQL variables var ( Version string // allows to set version on build diff --git a/repositories/token_test.go b/repositories/token_test.go index a0b38eb..a7eaf0e 100644 --- a/repositories/token_test.go +++ b/repositories/token_test.go @@ -42,14 +42,14 @@ func tokenmock(t *testing.T) (models.Token, *TokenDAO) { t.Error(err) } userDAO := NewUserRepository(db, userID.String(), username, appCache) - _, err = userDAO.Create(&models.User{ + _, err = userDAO.Create([]*models.User{{ ModelHiddenGUIDPK: common.ModelHiddenGUIDPK{ID: userID}, Username: username, Firstname: "Unit", Lastname: "Test", Email: "unittest@unittest.test", Active: true, - }) + }}) if err != nil { t.Error(err) } @@ -61,7 +61,7 @@ func tokenmock(t *testing.T) (models.Token, *TokenDAO) { return token, NewTokenRepository(db, token.UserID.String(), username, appCache) } -func validateResponse(t *testing.T, response *models.Token, token models.Token) { +func validateTokenResponse(t *testing.T, response *models.Token, token models.Token) { if response.Username != token.Username { t.Errorf("Token Username must be \"%s\" but is \"%s\"\n", token.Username, response.Username) } @@ -120,22 +120,25 @@ func validateResponse(t *testing.T, response *models.Token, token models.Token) func TestTokenRepository(t *testing.T) { token, tokenDAO := tokenmock(t) - response, err := tokenDAO.Create(&token) - if err != nil { + if response, err := tokenDAO.Create(&token); err != nil { t.Errorf("tokenDAO Create failed with error: %v\n", err) + } else { + validateTokenResponse(t, response, token) } - validateResponse(t, response, token) - response, err = tokenDAO.ReadByKey(token.Token.String()) - if err != nil { + if response, err := tokenDAO.ReadByKey(token.Token.String()); err != nil { t.Errorf("tokenDAO ReadByKey failed with error: %v\n", err) + } else { + validateTokenResponse(t, response, token) + } + { + response, err := tokenDAO.ReadAll() + switch { + case err != nil: + t.Errorf("tokenDAO ReadAll failed with error: %v\n", err) + case len(response) != 1: + t.Error("tokenDAO ReadAll failed with worng numer of responses") + default: + validateTokenResponse(t, response[0], token) + } } - validateResponse(t, response, token) - responses, err := tokenDAO.ReadAll() - if err != nil { - t.Errorf("tokenDAO ReadAll failed with error: %v\n", err) - } - if len(responses) != 1 { - t.Error("tokenDAO ReadAll failed with worng numer of responses") - } - validateResponse(t, responses[0], token) } diff --git a/repositories/user.go b/repositories/user.go index f8ca52b..678bfff 100644 --- a/repositories/user.go +++ b/repositories/user.go @@ -2,8 +2,10 @@ package repositories import ( + "git.devices.local/mawas/golang-api-skeleton/lib/apperrors" "git.devices.local/mawas/golang-api-skeleton/lib/cache" - model "git.devices.local/mawas/golang-api-skeleton/models" + "git.devices.local/mawas/golang-api-skeleton/lib/common" + "git.devices.local/mawas/golang-api-skeleton/models" "gorm.io/gorm" ) @@ -23,19 +25,39 @@ func NewUserRepository(db *gorm.DB, userID string, username string, cache cache. } } -func (dao *UserDAO) ReadByUsername(username string) (*model.User, error) { - result := model.User{} - err := dao.db.Preload("TokensRef").Where(&model.User{Username: username}).First(&result).Error - return &result, err +func (dao *UserDAO) ReadByUsername(username string) (*models.User, error) { + result := models.User{} + if err := dao.db.Preload("TokensRef").Where(&models.User{Username: username}).First(&result).Error; err != nil { + return nil, apperrors.NewError(apperrors.DBError, err.Error()) + } + return &result, nil +} + +func (dao *UserDAO) ReadAll() ([]*models.User, error) { + var result []*models.User + if err := dao.db.Preload("TokensRef").Find(&result).Error; err != nil { + return nil, apperrors.NewError(apperrors.DBError, err.Error()) + } + return result, nil } -func (dao *UserDAO) ReadAll() ([]*model.User, error) { - var result []*model.User - err := dao.db.Preload("TokensRef").Find(&result).Error - return result, err +func (dao *UserDAO) ReadSelection(ids []common.GUID) ([]*models.User, error) { + var result []*models.User + if err := dao.db.Preload("TokensRef").Where("id IN (?)", ids).Find(&result).Error; err != nil { + return nil, apperrors.NewError(apperrors.DBError, err.Error()) + } + return result, nil } -func (dao *UserDAO) Create(user *model.User) (*model.User, error) { - err := dao.db.Create(user).Error - return user, err +func (dao *UserDAO) Create(users []*models.User) ([]*models.User, error) { + db := dao.db.Begin() + for i := range users { + err := db.Create(users[i]).Error + if err != nil { + db.Rollback() + return nil, apperrors.NewError(apperrors.DBError, err.Error()) + } + } + db.Commit() + return users, nil } diff --git a/repositories/user_test.go b/repositories/user_test.go index e48e701..ae606ee 100644 --- a/repositories/user_test.go +++ b/repositories/user_test.go @@ -116,22 +116,31 @@ func validateUserResponse(t *testing.T, response *models.User, user models.User) func TestUserRepository(t *testing.T) { user, userDAO := userRepositoryMock(t) - response, err := userDAO.Create(&user) - if err != nil { - t.Errorf("userDAO Create failed with error: %v\n", err) - } - validateUserResponse(t, response, user) - response, err = userDAO.ReadByUsername(username) - if err != nil { + { + response, err := userDAO.Create([]*models.User{&user}) + switch { + case err != nil: + t.Errorf("userDAO Create failed with error: %v\n", err) + case len(response) != 1: + t.Error("userDAO Create response must be a list with 1 result in this test") + default: + validateUserResponse(t, response[0], user) + } + } + if response, err := userDAO.ReadByUsername(username); err != nil { t.Errorf("userDAO ReadByUsername failed with error: %v\n", err) + } else { + validateUserResponse(t, response, user) + } + { + response, err := userDAO.ReadAll() + switch { + case err != nil: + t.Errorf("userDAO ReadAll failed with error: %v\n", err) + case len(response) != 1: + t.Error("userDAO ReadAll failed with worng number of responses") + default: + validateUserResponse(t, response[0], user) + } } - validateUserResponse(t, response, user) - responses, err := userDAO.ReadAll() - if err != nil { - t.Errorf("userDAO ReadAll failed with error: %v\n", err) - } - if len(responses) != 1 { - t.Error("userDAO ReadAll failed with worng numer of responses") - } - validateUserResponse(t, responses[0], user) } diff --git a/services/token_test.go b/services/token_test.go index dfafa0c..84edf2a 100644 --- a/services/token_test.go +++ b/services/token_test.go @@ -44,14 +44,14 @@ func tokenServiceMock(t *testing.T) (models.Token, *TokenService) { } userRepository := repositories.NewUserRepository(db, userID.String(), username, appCache) userService := NewUserService(userRepository) - _, err = userService.Create(&models.User{ + _, err = userService.Create([]*models.User{{ ModelHiddenGUIDPK: common.ModelHiddenGUIDPK{ID: userID}, Username: username, Firstname: "Unit", Lastname: "Test", Email: "unittest@unittest.test", Active: true, - }) + }}) if err != nil { t.Error(err) } @@ -120,22 +120,25 @@ func validateResponse(t *testing.T, response *models.Token, token models.Token) func TestTokenRepository(t *testing.T) { token, tokenService := tokenServiceMock(t) - response, err := tokenService.Create(&token) - if err != nil { + if response, err := tokenService.Create(&token); err != nil { t.Errorf("tokenService Create failed with error: %v\n", err) + } else { + validateResponse(t, response, token) } - validateResponse(t, response, token) - response, err = tokenService.ReadByKey(token.Token.String()) - if err != nil { + if response, err := tokenService.ReadByKey(token.Token.String()); err != nil { t.Errorf("tokenService ReadByKey failed with error: %v\n", err) + } else { + validateResponse(t, response, token) + } + { + response, err := tokenService.ReadAll() + switch { + case err != nil: + t.Errorf("tokenService ReadAll failed with error: %v\n", err) + case len(response) != 1: + t.Error("tokenService ReadAll failed with worng numer of responses") + default: + validateResponse(t, response[0], token) + } } - validateResponse(t, response, token) - responses, err := tokenService.ReadAll() - if err != nil { - t.Errorf("tokenService ReadAll failed with error: %v\n", err) - } - if len(responses) != 1 { - t.Error("tokenService ReadAll failed with worng numer of responses") - } - validateResponse(t, responses[0], token) } diff --git a/services/user.go b/services/user.go index edd25f2..c2a51d5 100644 --- a/services/user.go +++ b/services/user.go @@ -1,13 +1,15 @@ package services import ( - model "git.devices.local/mawas/golang-api-skeleton/models" + "git.devices.local/mawas/golang-api-skeleton/lib/common" + "git.devices.local/mawas/golang-api-skeleton/models" ) type userRepository interface { - ReadByUsername(username string) (*model.User, error) - ReadAll() ([]*model.User, error) - Create(user *model.User) (*model.User, error) + ReadByUsername(username string) (*models.User, error) + ReadAll() ([]*models.User, error) + ReadSelection(ids []common.GUID) ([]*models.User, error) + Create(users []*models.User) ([]*models.User, error) } type UserService struct { @@ -20,23 +22,28 @@ func NewUserService(repo userRepository) *UserService { } // ReadByUsername just retrieves user using User DAO, here can be additional logic for processing data retrieved by DAOs -func (s *UserService) ReadByID(id interface{}) (*model.User, error) { +func (s *UserService) ReadByID(id interface{}) (*models.User, error) { return s.repo.ReadByUsername(id.(string)) } // ReadAll just retrieves user using User DAO, here can be additional logic for processing data retrieved by DAOs -func (s *UserService) ReadAll() ([]*model.User, error) { +func (s *UserService) ReadAll() ([]*models.User, error) { return s.repo.ReadAll() } +// ReadAll just retrieves user using User DAO, here can be additional logic for processing data retrieved by DAOs +func (s *UserService) ReadSelection(ids []common.GUID) ([]*models.User, error) { + return s.repo.ReadSelection(ids) +} + // Create just retrieves user using User DAO, here can be additional logic for processing data retrieved by DAOs -func (s *UserService) Create(user *model.User) (*model.User, error) { - return s.repo.Create(user) +func (s *UserService) Create(users []*models.User) ([]*models.User, error) { + return s.repo.Create(users) } // NewUser returns a new created User object -func NewUser(username, firstname, lastname, email string) model.User { - return model.User{ +func NewUser(username, firstname, lastname, email string) models.User { + return models.User{ Username: username, Firstname: firstname, Lastname: lastname, diff --git a/services/user_test.go b/services/user_test.go index 5aef930..51e9b1f 100644 --- a/services/user_test.go +++ b/services/user_test.go @@ -112,22 +112,29 @@ func validateUserResponse(t *testing.T, response *models.User, user models.User) func TestUserRepository(t *testing.T) { user, userService := userServiceMock(t) - response, err := userService.Create(&user) - if err != nil { + if response, err := userService.Create([]*models.User{&user}); err != nil { t.Errorf("userService Create failed with error: %v\n", err) - } - validateUserResponse(t, response, user) - response, err = userService.ReadByID(username) - if err != nil { + } else { + if len(response) != 1 { + t.Error("userService Create response must be a list with 1 result in this test") + } else { + validateUserResponse(t, response[0], user) + } + } + if response, err := userService.ReadByID(username); err != nil { t.Errorf("userService ReadByID failed with error: %v\n", err) + } else { + validateUserResponse(t, response, user) + } + { + response, err := userService.ReadAll() + switch { + case err != nil: + t.Errorf("userService ReadAll failed with error: %v\n", err) + case len(response) != 1: + t.Error("userService ReadAll failed with worng numer of responses") + default: + validateUserResponse(t, response[0], user) + } } - validateUserResponse(t, response, user) - responses, err := userService.ReadAll() - if err != nil { - t.Errorf("userService ReadAll failed with error: %v\n", err) - } - if len(responses) != 1 { - t.Error("userService ReadAll failed with worng numer of responses") - } - validateUserResponse(t, responses[0], user) }