diff --git a/go.mod b/go.mod index cf914c4..ddb9bcb 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect github.com/chenzhuoyu/iasm v0.9.1 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/fatih/color v1.16.0 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/gin-contrib/cache v1.2.0 // indirect github.com/gin-contrib/cors v1.5.0 // indirect @@ -21,24 +22,34 @@ require ( github.com/goccy/go-json v0.10.2 // indirect github.com/gomodule/redigo v1.8.9 // indirect github.com/google/uuid v1.6.0 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect github.com/jmoiron/sqlx v1.3.5 // indirect github.com/joho/godotenv v1.5.1 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/leodido/go-urn v1.4.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/memcachier/mc/v3 v3.0.3 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/orcaman/concurrent-map/v2 v2.0.1 // indirect github.com/pelletier/go-toml/v2 v2.1.1 // indirect github.com/redis/go-redis/v9 v9.5.1 // indirect github.com/robfig/go-cache v0.0.0-20130306151617-9fc39e0dbf62 // indirect github.com/sirupsen/logrus v1.9.3 // indirect + github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect + github.com/uptrace/bun v1.1.17 // indirect + github.com/uptrace/bun/dialect/mysqldialect v1.1.17 // indirect + github.com/uptrace/bun/extra/bundebug v1.1.17 // indirect + github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect golang.org/x/arch v0.7.0 // indirect golang.org/x/crypto v0.20.0 // indirect + golang.org/x/mod v0.14.0 // indirect golang.org/x/net v0.21.0 // indirect golang.org/x/sys v0.17.0 // indirect golang.org/x/text v0.14.0 // indirect diff --git a/go.sum b/go.sum index 33f3489..d7e4ca9 100644 --- a/go.sum +++ b/go.sum @@ -17,6 +17,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/gin-contrib/cache v1.2.0 h1:WA+AJR4kmHDTaLLShCHo/IeWVmmGRZ3Lsr3JQ46tFlE= @@ -47,6 +49,8 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +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/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= @@ -60,6 +64,9 @@ github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgSh github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= @@ -72,6 +79,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/orcaman/concurrent-map/v2 v2.0.1 h1:jOJ5Pg2w1oeB6PeDurIYf6k9PQ+aTITr/6lP/L/zp6c= +github.com/orcaman/concurrent-map/v2 v2.0.1/go.mod h1:9Eq3TG2oBe5FirmYWQfYO5iH1q0Jv47PLaNK++uCdOM= github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -92,18 +101,33 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/uptrace/bun v1.1.17 h1:qxBaEIo0hC/8O3O6GrMDKxqyT+mw5/s0Pn/n6xjyGIk= +github.com/uptrace/bun v1.1.17/go.mod h1:hATAzivtTIRsSJR4B8AXR+uABqnQxr3myKDKEf5iQ9U= +github.com/uptrace/bun/dialect/mysqldialect v1.1.17 h1:CsaZu+C3hW6jH5XnbQWPeZbHOoeURRpX9wd9wNy9fYU= +github.com/uptrace/bun/dialect/mysqldialect v1.1.17/go.mod h1:PDT12yHB0yLidZWFoPjhXfEKvsu7tLyjY67+OSMQsVw= +github.com/uptrace/bun/extra/bundebug v1.1.17 h1:LcZ8DzyyGdXAmbUqmnCpBq7TPFegMp59FGy+uzEE21c= +github.com/uptrace/bun/extra/bundebug v1.1.17/go.mod h1:FOwNaBEGGChv3qBVh3pz3TPlUuikZ93qKjd/LJdl91o= +github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= +github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc= golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/crypto v0.20.0 h1:jmAMJJZXr5KiCw05dfYK9QnqaqKLYXijU23lsEdcQqg= golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ= +golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= diff --git a/handlers/catalog/endpoints/catalog.go b/handlers/catalog/endpoints/catalog.go index 1988a83..9bb2546 100644 --- a/handlers/catalog/endpoints/catalog.go +++ b/handlers/catalog/endpoints/catalog.go @@ -1,41 +1,78 @@ package endpoints import ( + "context" "fmt" - "github.com/gin-gonic/gin" + cmap "github.com/orcaman/concurrent-map/v2" "relynolli-server/models" - "relynolli-server/services" - "strconv" + "relynolli-server/status" + "relynolli-server/storage" + "time" + + "github.com/gin-gonic/gin" + // "relynolli-server/models" + // "relynolli-server/services" + // "strconv" ) -func (h *handlers) GetCatalogItems(c *gin.Context) { +type GetCatalogItemsRequest struct { + Limit int `form:"limit" ` + Page int `form:"page"` +} - limit, _ := strconv.Atoi(c.DefaultQuery("limit", "10")) - page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) - offset := (page - 1) * limit - if c.DefaultQuery("isFilter", "0") == "0" { - c.JSON(200, services.GetCatalogItems(limit, offset)) +func (h *handlers) GetCatalogItems(c *gin.Context) { + queries := cmap.New[[]string]() + for key, val := range c.Request.URL.Query() { + queries.Set(key, val) + } + + ctx := context.Background() + meta := models.Meta{ + RequestStarted: time.Now().Unix(), + } + + LPQuery := new(GetCatalogItemsRequest) + LPError := c.ShouldBindQuery(LPQuery) + if LPError != nil { + meta.RequestFinished = time.Now().Unix() + c.JSON(400, models.Response{ + Status: status.STATUS_BAD_REQUEST, + Info: "Limit and page query params should be integer numbers", + Meta: &meta, + }) return } - c.JSON(200, services.FilterCatalogItems(c.Request.URL.Query(), limit, offset)) + if LPQuery.Page == 0 { + LPQuery.Page = 1 + } + if LPQuery.Limit == 0 { + LPQuery.Limit = 10 + } + + s := storage.NewStorageCatalog() + count, items, err := s.GetCatalogItems(ctx, queries, LPQuery.Limit, (LPQuery.Page-1)*LPQuery.Limit) + + if err != nil { + meta.RequestFinished = time.Now().Unix() + c.JSON(500, + models.Response{ + Status: status.STATUS_SERVER_ERROR, + Info: fmt.Sprintf("Cannot resolve request. Details: %s", err.Error()), + Meta: &meta}) + } + + meta.Limit = LPQuery.Limit + meta.Page = LPQuery.Page + meta.RequestFinished = time.Now().Unix() + meta.Count = count + + c.JSON(200, models.Response{ + Status: status.STATUS_OK, + Data: items, + Meta: &meta, + }) + } func (h *handlers) GetCatalogItem(c *gin.Context) { - code := c.Param("code") - if code == "" { - c.JSON(400, models.Response{Status: 400, Info: "product \"Code\" should be provided"}) - return - } - - resp, err := services.GetCatalogItem(code) - if err != nil { - c.JSON(404, models.Response{Status: 404, Info: err.Error()}) - return - } - - c.JSON(200, resp) -} - -func (h *handlers) Count(c *gin.Context) { - c.JSON(200, models.Response{Status: 200, Info: fmt.Sprintf("%d", services.GetCatalogItemsCount())}) } diff --git a/handlers/catalog/endpoints/ep.go b/handlers/catalog/endpoints/ep.go index 8348297..46b73b8 100644 --- a/handlers/catalog/endpoints/ep.go +++ b/handlers/catalog/endpoints/ep.go @@ -7,8 +7,6 @@ type handlers struct{} type Handlers interface { GetFilters(c *gin.Context) GetCatalogItems(c *gin.Context) - GetCatalogItem(c *gin.Context) - Count(c *gin.Context) } func GetHandlers() Handlers { diff --git a/handlers/catalog/endpoints/filters.go b/handlers/catalog/endpoints/filters.go index 9f44366..7598675 100644 --- a/handlers/catalog/endpoints/filters.go +++ b/handlers/catalog/endpoints/filters.go @@ -1,38 +1,41 @@ package endpoints import ( - "encoding/json" + "context" + "fmt" "github.com/gin-gonic/gin" - "relynolli-server/internal" + "relynolli-server/models" + "relynolli-server/status" + "relynolli-server/storage" + "time" ) -type filterValues struct { - Id int `json:"id"` - Value string `json:"value"` -} - -type filterStruct struct { - Id int `json:"id"` - Code string `json:"code"` - Name string `json:"name"` - Values []filterValues `json:"values"` - valuesString []byte -} - func (h *handlers) GetFilters(c *gin.Context) { - stmt := "select * from api_filter;" - var responseData []filterStruct - db := internal.InitDatabase() - rows := db.Query(stmt) - for rows.Next() { - filter := filterStruct{} - // grab data from db - rows.Scan(&filter.Id, &filter.Code, &filter.Name, &filter.valuesString) - json.Unmarshal(filter.valuesString, &filter.Values) - // parse data as json - responseData = append(responseData, filter) + meta := models.Meta{ + RequestStarted: time.Now().Unix(), } - c.JSON(200, responseData) + s := storage.NewStorageCatalog() + ctx := context.Background() + count, items, err := s.GetFilters(ctx) + + if err != nil { + meta.RequestFinished = time.Now().Unix() + c.JSON(500, models.Response{ + Status: status.STATUS_SERVER_ERROR, + Info: fmt.Sprintf("Internal Server Error: %s", err.Error()), + Meta: &meta, + }) + } + + meta.RequestFinished = time.Now().Unix() + meta.Count = count + + c.JSON(200, models.Response{ + Status: status.STATUS_OK, + Data: &items, + Meta: &meta, + }) + } diff --git a/handlers/catalog/routes.go b/handlers/catalog/routes.go index 1623ed7..05f786f 100644 --- a/handlers/catalog/routes.go +++ b/handlers/catalog/routes.go @@ -1,11 +1,16 @@ package catalog import ( + "github.com/gin-contrib/cache" + "os" "relynolli-server/handlers/catalog/endpoints" "relynolli-server/internal" "time" - "github.com/gin-contrib/cache" + // "relynolli-server/internal" + // "time" + + // "github.com/gin-contrib/cache" "github.com/gin-gonic/gin" ) @@ -13,8 +18,15 @@ func HandleRoutes(parent *gin.RouterGroup) { h := endpoints.GetHandlers() cacheStore := internal.InitCacheStore() catalog := parent.Group("/catalog") - catalog.GET("/filters", cache.CachePage(cacheStore, 15, h.GetFilters)) - catalog.GET("/count", cache.CachePage(cacheStore, 15 * time.Minute, h.Count)) - catalog.GET("", cache.CachePage(cacheStore, 15 * time.Minute, h.GetCatalogItems)) - catalog.GET("/:code", cache.CachePage(cacheStore, 15 * time.Minute, h.GetCatalogItem)) + if os.Getenv("IS_PROD") == "1" { + // Caching for production usage + catalog.GET("", cache.CachePage(cacheStore, 15*time.Minute, h.GetCatalogItems)) + } + + catalog.GET("", h.GetCatalogItems) + catalog.GET("/filters", h.GetFilters) + + // catalog.GET("/filters", cache.CachePage(cacheStore, 15, h.GetFilters)) + // catalog.GET("/count", cache.CachePage(cacheStore, 15 * time.Minute, h.Count)) + // catalog.GET("/:code", cache.CachePage(cacheStore, 15 * time.Minute, h.GetCatalogItem)) } diff --git a/handlers/order/routes.go b/handlers/order/routes.go index cc10a66..faa9589 100644 --- a/handlers/order/routes.go +++ b/handlers/order/routes.go @@ -1,8 +1,9 @@ package order import ( - "github.com/gin-gonic/gin" "relynolli-server/handlers/order/endpoints" + + "github.com/gin-gonic/gin" ) func HandleRoutes(parent *gin.RouterGroup) { diff --git a/handlers/routers.go b/handlers/routers.go index c6778ad..1a1f129 100644 --- a/handlers/routers.go +++ b/handlers/routers.go @@ -2,16 +2,16 @@ package handlers import ( "github.com/gin-gonic/gin" - "relynolli-server/handlers/cart" + // "relynolli-server/handlers/cart" "relynolli-server/handlers/catalog" - "relynolli-server/handlers/order" - "relynolli-server/handlers/validate" + // "relynolli-server/handlers/order" + // "relynolli-server/handlers/validate" ) func InitializeRouter(router *gin.Engine) { APIV1Router := router.Group("/api/v1") catalog.HandleRoutes(APIV1Router) - cart.HandleRoutes(APIV1Router) - order.HandleRoutes(APIV1Router) - validate.HandleRoutes(APIV1Router) + // cart.HandleRoutes(APIV1Router) + // order.HandleRoutes(APIV1Router) + // validate.HandleRoutes(APIV1Router) } diff --git a/internal/database.go b/internal/database.go index 325c715..525b62d 100644 --- a/internal/database.go +++ b/internal/database.go @@ -3,25 +3,22 @@ package internal import ( "database/sql" "fmt" + "github.com/uptrace/bun/extra/bundebug" "log" "os" "sync" - "time" _ "github.com/go-sql-driver/mysql" - "github.com/jmoiron/sqlx" + "github.com/uptrace/bun" + "github.com/uptrace/bun/dialect/mysqldialect" ) type database struct { - instance *sqlx.DB + instance *bun.DB } type Database interface { - GetInstance() *sqlx.DB - Close() - Query(stmt string) *sqlx.Rows - Execute(stmt string) sql.Result - FetchRows(stmt string, dest interface{}) + GetInstance() *bun.DB } var ( @@ -30,27 +27,31 @@ var ( ) func initialize() { - db, err := sqlx.Open("mysql", fmt.Sprintf( + db, err := sql.Open("mysql", fmt.Sprintf( "%s:%s@tcp(%s)/%s", os.Getenv("MYSQL_USER"), os.Getenv("MYSQL_PASSWORD"), os.Getenv("MYSQL_HOST"), os.Getenv("MYSQL_DATABASE"))) - db.SetConnMaxLifetime(time.Minute * 3) - db.SetMaxOpenConns(3) - db.SetMaxIdleConns(3) - if err != nil { panic(err) } - conErr := db.Ping() + + // Resolve instances of bun + + ormDb := bun.NewDB(db, mysqldialect.New()) + ormDb.AddQueryHook(bundebug.NewQueryHook()) + + // Check Connection + conErr := ormDb.Ping() + if conErr != nil { panic(conErr) } log.Println("Connection to db succeded") - instance = &database{instance: db} + instance = &database{instance: ormDb} } func InitDatabase() Database { @@ -60,39 +61,6 @@ func InitDatabase() Database { return instance } -func (db *database) GetInstance() *sqlx.DB { - return db.instance -} - -func (db *database) Close() { - defer log.Println("Connection to database was closed") - err := db.instance.Close() - if err != nil { - return - } -} - -func (db *database) Query(stmt string) *sqlx.Rows { - rows, err := db.instance.Queryx(stmt) - if err != nil { - return nil - } - return rows -} - -func (db *database) Execute(stmt string) sql.Result { - result, err := db.instance.Exec(stmt) - if err != nil { - log.Println(err) - } - return result -} - -type FetchRowStruct []interface{} - -func (db *database) FetchRows(stmt string, dest interface{}) { - err := db.instance.Select(dest, stmt) - if err != nil { - log.Println(err) - } +func (d *database) GetInstance() *bun.DB { + return d.instance } diff --git a/main.go b/main.go index 447076f..c4151e0 100644 --- a/main.go +++ b/main.go @@ -28,7 +28,7 @@ func main() { rdb := internal.InitRedis() handlers.InitializeRouter(server) - defer db.Close() + defer db.GetInstance().Close() defer rdb.Close() gracefullyShutDown := make(chan os.Signal, 1) diff --git a/models/catalog.go b/models/catalog.go index 0acd42c..06eba4a 100644 --- a/models/catalog.go +++ b/models/catalog.go @@ -1,47 +1,47 @@ package models -type CatalogStruct struct { - Id int - Code string - Name string - IsActive int `json:"is_active" db:"is_active"` - Properties []byte - DetailText string `json:"detailText" db:"detailText"` - Price []byte - AvailableQuantity int `json:"availableQuantity,omitempty" db:"available_quantity"` -} +// type CatalogStruct struct { +// Id int +// Code string +// Name string +// IsActive int `json:"is_active" db:"is_active"` +// Properties []byte +// DetailText string `json:"detailText" db:"detailText"` +// Price []byte +// AvailableQuantity int `json:"availableQuantity,omitempty" db:"available_quantity"` +// } -type CatalogStructWeb struct { - Id int `json:"id"` - Code string `json:"code"` - Name string `json:"name"` - IsActive int `json:"is_active" db:"is_active"` - Properties map[string]interface{} `json:"properties"` - DetailText string `json:"detailText" db:"detailText"` - Price map[string]interface{} `json:"price"` - AvailableQuantity int `json:"availableQuantity,omitempty" db:"available_quantity"` -} +// type CatalogStructWeb struct { +// Id int `json:"id"` +// Code string `json:"code"` +// Name string `json:"name"` +// IsActive int `json:"is_active" db:"is_active"` +// Properties map[string]interface{} `json:"properties"` +// DetailText string `json:"detailText" db:"detailText"` +// Price map[string]interface{} `json:"price"` +// AvailableQuantity int `json:"availableQuantity,omitempty" db:"available_quantity"` +// } -type CatalogWithQuantityWeb struct { - Id int `json:"id"` - Code string `json:"code"` - Name string `json:"name"` - IsActive int `json:"is_active"` - Properties map[string]interface{} `json:"properties"` - DetailText string `json:"detailText"` - Price map[string]interface{} `json:"price"` - Quantity int `json:"quantity"` - AvailableQuantity int `json:"available_quantity" db:"available_quantity"` -} +// type CatalogWithQuantityWeb struct { +// Id int `json:"id"` +// Code string `json:"code"` +// Name string `json:"name"` +// IsActive int `json:"is_active"` +// Properties map[string]interface{} `json:"properties"` +// DetailText string `json:"detailText"` +// Price map[string]interface{} `json:"price"` +// Quantity int `json:"quantity"` +// AvailableQuantity int `json:"available_quantity" db:"available_quantity"` +// } -type CatalogWithQuantity struct { - Id int - Code string - Name string - IsActive int `json:"is_active" db:"is_active"` - Properties []byte - DetailText string `json:"detailText" db:"detailText"` - Price []byte - Quantity int `json:"quantity"` - AvailableQuantity int `json:"available_quantity" db:"available_quantity"` -} +// type CatalogWithQuantity struct { +// Id int +// Code string +// Name string +// IsActive int `json:"is_active" db:"is_active"` +// Properties []byte +// DetailText string `json:"detailText" db:"detailText"` +// Price []byte +// Quantity int `json:"quantity"` +// AvailableQuantity int `json:"available_quantity" db:"available_quantity"` +// } diff --git a/models/catalog/db.go b/models/catalog/db.go new file mode 100644 index 0000000..a06b8d8 --- /dev/null +++ b/models/catalog/db.go @@ -0,0 +1,52 @@ +package catalog + +import "github.com/uptrace/bun" + +type DBCatalog struct { + bun.BaseModel `bun:"select:api_catalog"` + Id int64 `bun:"id" json:"id"` + Code string `bun:"code" json:"code"` + Name string `bun:"name" json:"name"` + IsActive bool `bun:"is_active,type:integer" json:"isActive"` + Properties *DBCatalogProperties `bun:"properties" json:"properties"` + DetailText string `bun:"detailText" json:"detailText"` + Price *DBCatalogPrice `bun:"price" json:"price"` + AvailableQuantity int64 `bun:"available_quantity" json:"availableQuantity"` +} + +type DBCatalogProperties struct { + Acea string `json:"acea,omitempty"` + Width string `json:"width,omitempty"` + Height string `json:"height,omitempty"` + Length string `json:"length,omitempty"` + Volume string `json:"volume,omitempty"` + Weight string `json:"weight,omitempty"` + Mileage string `json:"mileage,omitempty"` + BoxType string `json:"box_type,omitempty"` + Category string `json:"category,omitempty"` + OilType string `json:"oil_type,omitempty"` + Documents []string `json:"documents,omitempty"` + UseAreas string `json:"use_areas,omitempty"` + Viscosity string `json:"viscosity,omitempty"` + AcidIndex string `json:"acid_index,omitempty"` + MainImage []string `json:"main_image,omitempty"` + PourPoint string `json:"pour_point,omitempty"` + FlashPoint string `json:"flash_point,omitempty"` + Subcategory string `json:"subcategory,omitempty"` + VendorCode string `json:"vendor_code,omitempty"` + ApiStandart string `json:"api_standart,omitempty"` + Requirements string `json:"requirements,omitempty"` + ViscosityIndex string `json:"viscosity_index,omitempty"` + ViscosityKinematic string `json:"viscosity_kinematic,omitempty"` + TribologicalProperties string `json:"tribological_properties,omitempty"` +} + +type DBCatalogPrice struct { + BASE float64 `json:"BASE"` + OPTMAX float64 `json:"OPTMAX,omitempty"` + OPTMIN float64 `json:"OPTMIN,omitempty"` + MOC float64 `json:"Мелко-Оптовая Цена (МОЦ),omitempty"` + KOC float64 `json:"Крупно-Оптовая Цена (КОЦ),omitempty"` + MCP float64 `json:"Минимальная Цена Продаж (МЦП),omitempty"` + RRC float64 `json:"Рекомендуемая Розничная цена (РРЦ),omitempty"` +} diff --git a/models/catalog/domain.go b/models/catalog/domain.go new file mode 100644 index 0000000..d7fc61b --- /dev/null +++ b/models/catalog/domain.go @@ -0,0 +1,13 @@ +package catalog + +type DomainCatalog struct { + // bun.BaseModel `bun:"select:api_catalog"` + Id int64 + Code string + Name string + IsActive bool `bun:"is_active,type:integer"` + Properties string `bun:"properties"` + DetailText string `bun:"detailText"` + Price string `bun:"price"` + AvailableQunatity int64 `bun:"available_quantity"` +} diff --git a/models/catalog/requests.go b/models/catalog/requests.go new file mode 100644 index 0000000..e571e24 --- /dev/null +++ b/models/catalog/requests.go @@ -0,0 +1 @@ +package catalog diff --git a/models/filters/db.go b/models/filters/db.go new file mode 100644 index 0000000..bcd7fcd --- /dev/null +++ b/models/filters/db.go @@ -0,0 +1,16 @@ +package filters + +import "github.com/uptrace/bun" + +type DBFilter struct { + bun.BaseModel `bun:"select:api_filter"` + Id int64 `bun:"id" json:"id"` + Code string `bun:"code" json:"code"` + Name string `bun:"name" json:"name"` + Values *[]DBFilterValues `bun:"values" json:"values"` +} + +type DBFilterValues struct { + Id int64 `json:"id"` + Value string `json:"value"` +} diff --git a/models/response.go b/models/response.go index 8148836..a6346c9 100644 --- a/models/response.go +++ b/models/response.go @@ -1,7 +1,19 @@ package models +import "relynolli-server/status" + type Response struct { - Status int `json:"status"` - Info string `json:"info,omitempty"` - Data interface{} `json:"data,omitempty"` + Status status.Status `json:"status"` + Info string `json:"info,omitempty"` + Data interface{} `json:"data,omitempty"` + + Meta *Meta `json:"meta"` +} + +type Meta struct { + RequestStarted int64 `json:"requestStarted"` + RequestFinished int64 `json:"requestFinished"` + Page int `json:"page,omitempty"` + Limit int `json:"limit,omitempty"` + Count int `json:"count,omitempty"` } diff --git a/status/status.go b/status/status.go new file mode 100644 index 0000000..cee4ee3 --- /dev/null +++ b/status/status.go @@ -0,0 +1,10 @@ +package status + +type Status string + +const ( + STATUS_OK Status = "OK" + STATUS_NOT_FOUND Status = "not_found" + STATUS_BAD_REQUEST Status = "bad_request" + STATUS_SERVER_ERROR Status = "internal_server_error" +) diff --git a/storage/cart.go b/storage/cart.go new file mode 100644 index 0000000..a672af2 --- /dev/null +++ b/storage/cart.go @@ -0,0 +1,15 @@ +package storage + +import "context" + +type StorageCart interface { + +} + +func (s *storage) GetCartItem(ctx context.Context) { + +} + +func (s *storage) GetCartItems(ctx context.Context) { + +} diff --git a/storage/catalog.go b/storage/catalog.go new file mode 100644 index 0000000..434a9a7 --- /dev/null +++ b/storage/catalog.go @@ -0,0 +1,83 @@ +package storage + +import ( + "context" + "fmt" + cmap "github.com/orcaman/concurrent-map/v2" + "github.com/uptrace/bun" + "relynolli-server/internal" + "relynolli-server/models/catalog" + filters2 "relynolli-server/models/filters" + "slices" +) + +type StorageCatalog interface { + GetCatalogItem(ctx context.Context, id *int64) (*catalog.DBCatalog, error) + GetCatalogItems(ctx context.Context, filters cmap.ConcurrentMap[string, []string], limit int, offset int) (int, *[]catalog.DBCatalog, error) + GetFilters(ctx context.Context) (int, *[]filters2.DBFilter, error) +} + +func NewStorageCatalog() StorageCatalog { + if instance == nil { + instance = &storage{ + db: internal.InitDatabase().GetInstance(), + } + } + return instance +} + +func (s *storage) GetCatalogItem(ctx context.Context, id *int64) (*catalog.DBCatalog, error) { + db := internal.InitDatabase().GetInstance() + model := new(catalog.DBCatalog) + + err := db.NewSelect().Model(model).Where("id = ?", id).Where("available_quantity > 0").Where("is_available = 1").Scan(ctx) + + if err != nil { + return nil, err + } + return model, nil +} + +func buildFilterGroup(ctx context.Context, q *bun.SelectQuery, filters *cmap.ConcurrentMap[string, []string]) *bun.SelectQuery { + db := internal.InitDatabase().GetInstance() + availableFilters := new([]filters2.DBFilter) + + //Get filters + db.NewSelect().Model(availableFilters).Where("code in (?)", bun.In(filters.Keys())).Scan(ctx) + + for _, filter := range filters.Keys() { + if !slices.ContainsFunc(*availableFilters, func(elem filters2.DBFilter) bool { + return elem.Code == filter + }) { + continue + } + q = q.WhereGroup(" AND ", func(query *bun.SelectQuery) *bun.SelectQuery { + values, _ := filters.Get(filter) + for _, val := range values { + query = q.WhereOr(fmt.Sprintf("properties->>'$.%s' = ?", filter), val) + } + return query + }) + } + return q + +} + +func (s *storage) GetCatalogItems(ctx context.Context, filters cmap.ConcurrentMap[string, []string], limit int, offset int) (int, *[]catalog.DBCatalog, error) { + db := internal.InitDatabase().GetInstance() + model := new([]catalog.DBCatalog) + filterQuery := db.NewSelect().Model(model).Where("is_active = 1").Where("available_quantity > 0") + count, _ := buildFilterGroup(ctx, filterQuery, &filters).Limit(limit).Offset(offset).ScanAndCount(ctx) + + return count, model, nil +} + +func (s *storage) GetFilters(ctx context.Context) (int, *[]filters2.DBFilter, error) { + models := new([]filters2.DBFilter) + db := internal.InitDatabase().GetInstance() + count, err := db.NewSelect().Model(models).ScanAndCount(ctx) + if err != nil { + return 0, nil, err + } + return count, models, nil +} diff --git a/storage/storage.go b/storage/storage.go new file mode 100644 index 0000000..03d6e70 --- /dev/null +++ b/storage/storage.go @@ -0,0 +1,11 @@ +package storage + +import ( + "github.com/uptrace/bun" +) + +type storage struct { + db *bun.DB +} + +var instance *storage = nil