diff --git a/handlers/article/endpoints/ep.go b/handlers/article/endpoints/ep.go new file mode 100644 index 0000000..e69de29 diff --git a/handlers/article/routes.go b/handlers/article/routes.go new file mode 100644 index 0000000..e69de29 diff --git a/handlers/cart/endpoints/cart.go b/handlers/cart/endpoints/cart.go index 6a52f0a..a086d65 100644 --- a/handlers/cart/endpoints/cart.go +++ b/handlers/cart/endpoints/cart.go @@ -1,35 +1,75 @@ package endpoints import ( + "context" + "fmt" "relynolli-server/models" - "relynolli-server/services" - "strconv" + "relynolli-server/status" + "relynolli-server/storage" + "time" "github.com/gin-gonic/gin" ) +type getCartItemsRequest struct { + FuserId int64 `form:"fuserId"` +} + func (h *handlers) GetCartItems(c *gin.Context) { + ctx := context.Background() + query := new(getCartItemsRequest) + meta := models.Meta{ + RequestStarted: time.Now().Unix(), + } - fuserId := c.Query("fuserId") - if fuserId == "" { - c.JSON(400, models.Response{Status: 400, Info: "\"fuserId\" should be provided"}) + err := c.ShouldBindQuery(query) + if err != nil || query.FuserId == 0 { + meta.RequestFinished = time.Now().Unix() + c.JSON(400, models.Response{ + Status: status.STATUS_BAD_REQUEST, + Info: "\"fuserId\" should be provided and be integer number", + Meta: &meta}) return } - idx, err := strconv.Atoi(fuserId) + s := storage.NewStorageCart() - if err != nil { - c.JSON(400, models.Response{Status: 400, Info: "\"fuserId should be an integer number\""}) - return - } + items, _ := s.GetCartItems(ctx, query.FuserId) - c.JSON(200, services.GetCartItems(idx)) + meta.RequestFinished = time.Now().Unix() + c.JSON(200, models.Response{ + Status: status.STATUS_OK, + Data: &items, + Meta: &meta, + }) } func (h *handlers) CreateFUser(c *gin.Context) { - lastInsertId := services.CreateFuser() + s := storage.NewStorageCart() + ctx := context.Background() + meta := models.Meta{ + RequestStarted: time.Now().Unix(), + } - c.JSON(201, gin.H{ - "fuserId": lastInsertId, + fuserId, fuser, err := s.CreateFuser(ctx) + if err != nil { + meta.RequestFinished = time.Now().Unix() + c.JSON(500, models.Response{ + Status: status.STATUS_SERVER_ERROR, + Info: fmt.Sprintf("Error: %s", err.Error()), + Meta: &meta, + }) + return + } + + meta.RequestFinished = time.Now().Unix() + c.JSON(201, models.Response{ + Status: status.STATUS_OK, + Info: "New Fuser has created", + Data: &gin.H{ + "fuserId": fuserId, + "fuser": &fuser, + }, + Meta: &meta, }) } diff --git a/handlers/cart/endpoints/ep.go b/handlers/cart/endpoints/ep.go index 5833d09..b022b53 100644 --- a/handlers/cart/endpoints/ep.go +++ b/handlers/cart/endpoints/ep.go @@ -7,10 +7,10 @@ type handlers struct{} type Handlers interface { GetCartItems(c *gin.Context) CreateFUser(c *gin.Context) - - CreateCartItem(c *gin.Context) - UpdateCartItem(c *gin.Context) - DeleteCartItem(c *gin.Context) + // + //CreateCartItem(c *gin.Context) + //UpdateCartItem(c *gin.Context) + //DeleteCartItem(c *gin.Context) } func GetHandlers() Handlers { diff --git a/handlers/cart/endpoints/item.go b/handlers/cart/endpoints/item.go index 65b84ae..b28836b 100644 --- a/handlers/cart/endpoints/item.go +++ b/handlers/cart/endpoints/item.go @@ -1,14 +1,5 @@ package endpoints -import ( - "fmt" - "net/http" - "relynolli-server/models" - "relynolli-server/services" - - "github.com/gin-gonic/gin" -) - type createCartItemRequest struct { FuserId int `json:"fuserId"` PriceTypeId int `json:"priceTypeId,omitempty"` @@ -27,50 +18,50 @@ type deleteCartRequest struct { ProductId int `json:"productId"` } -func (h *handlers) CreateCartItem(c *gin.Context) { - req := createCartItemRequest{} - err := c.ShouldBindJSON(&req) - - if err != nil { - c.JSON(http.StatusBadRequest, models.Response{Status: http.StatusBadRequest, Info: fmt.Sprintf("Bad request. Error info: %s", err.Error())}) - return - } - services.AddItemToCart(req.FuserId, req.ProductId) - - c.JSON(http.StatusCreated, models.Response{Status: http.StatusCreated, Info: fmt.Sprintf("Item %d has added to cart", req.ProductId)}) -} - -func (h *handlers) UpdateCartItem(c *gin.Context) { - req := updateCartRequest{} - err := c.ShouldBindJSON(&req) - - if err != nil { - c.JSON(http.StatusBadRequest, models.Response{Status: http.StatusBadRequest, Info: fmt.Sprintf("Bad request. Error info: %s", err.Error())}) - return - } - - err = services.UpdateCartItem(req.FuserId, req.ProductId, req.Quantity) - - if err != nil { - c.JSON(http.StatusBadRequest, models.Response{Status: http.StatusBadRequest, Info: fmt.Sprintf("Bad request. Error info: %s", err.Error())}) - return - } - - c.JSON(http.StatusOK, models.Response{Status: http.StatusOK}) - -} - -func (h *handlers) DeleteCartItem(c *gin.Context) { - - req := deleteCartRequest{} - err := c.ShouldBindJSON(&req) - - if err != nil { - c.JSON(400, models.Response{Status: 400, Info: fmt.Sprintf("Bad request. Error info: %s", err.Error())}) - return - } - - services.DeleteCartItem(req.FuserId, req.ProductId) - - c.JSON(http.StatusNoContent, models.Response{Status: http.StatusNoContent}) -} +//func (h *handlers) CreateCartItem(c *gin.Context) { +// req := createCartItemRequest{} +// err := c.ShouldBindJSON(&req) +// +// if err != nil { +// c.JSON(http.StatusBadRequest, models.Response{Status: http.StatusBadRequest, Info: fmt.Sprintf("Bad request. Error info: %s", err.Error())}) +// return +// } +// services.AddItemToCart(req.FuserId, req.ProductId) +// +// c.JSON(http.StatusCreated, models.Response{Status: http.StatusCreated, Info: fmt.Sprintf("Item %d has added to cart", req.ProductId)}) +//} +// +//func (h *handlers) UpdateCartItem(c *gin.Context) { +// req := updateCartRequest{} +// err := c.ShouldBindJSON(&req) +// +// if err != nil { +// c.JSON(http.StatusBadRequest, models.Response{Status: http.StatusBadRequest, Info: fmt.Sprintf("Bad request. Error info: %s", err.Error())}) +// return +// } +// +// err = services.UpdateCartItem(req.FuserId, req.ProductId, req.Quantity) +// +// if err != nil { +// c.JSON(http.StatusBadRequest, models.Response{Status: http.StatusBadRequest, Info: fmt.Sprintf("Bad request. Error info: %s", err.Error())}) +// return +// } +// +// c.JSON(http.StatusOK, models.Response{Status: http.StatusOK}) +// +//} +// +//func (h *handlers) DeleteCartItem(c *gin.Context) { +// +// req := deleteCartRequest{} +// err := c.ShouldBindJSON(&req) +// +// if err != nil { +// c.JSON(400, models.Response{Status: 400, Info: fmt.Sprintf("Bad request. Error info: %s", err.Error())}) +// return +// } +// +// services.DeleteCartItem(req.FuserId, req.ProductId) +// +// c.JSON(http.StatusNoContent, models.Response{Status: http.StatusNoContent}) +//} diff --git a/handlers/cart/routes.go b/handlers/cart/routes.go index ebe00c8..9b1fc52 100644 --- a/handlers/cart/routes.go +++ b/handlers/cart/routes.go @@ -8,15 +8,15 @@ import ( func HandleRoutes(parent *gin.RouterGroup) { h := endpoints.GetHandlers() cart := parent.Group("/cart") - itemRouter := cart.Group("/item") + //itemRouter := cart.Group("/item") { cart.GET("", h.GetCartItems) cart.POST("", h.CreateFUser) } - { - itemRouter.POST("", h.CreateCartItem) - itemRouter.PATCH("", h.UpdateCartItem) - itemRouter.DELETE("", h.DeleteCartItem) - } + //{ + // itemRouter.POST("", h.CreateCartItem) + // itemRouter.PATCH("", h.UpdateCartItem) + // itemRouter.DELETE("", h.DeleteCartItem) + //} } diff --git a/handlers/catalog/endpoints/catalog.go b/handlers/catalog/endpoints/catalog.go index 9bb2546..b63256d 100644 --- a/handlers/catalog/endpoints/catalog.go +++ b/handlers/catalog/endpoints/catalog.go @@ -3,12 +3,13 @@ package endpoints import ( "context" "fmt" - cmap "github.com/orcaman/concurrent-map/v2" "relynolli-server/models" "relynolli-server/status" "relynolli-server/storage" "time" + cmap "github.com/orcaman/concurrent-map/v2" + "github.com/gin-gonic/gin" // "relynolli-server/models" // "relynolli-server/services" @@ -74,5 +75,43 @@ func (h *handlers) GetCatalogItems(c *gin.Context) { } -func (h *handlers) GetCatalogItem(c *gin.Context) { +type catalogItemReq struct { + Code string `uri:"code"` +} + +func (h *handlers) GetCatalogItem(c *gin.Context) { + ctx := context.Background() + meta := models.Meta{ + RequestStarted: time.Now().Unix(), + } + s := storage.NewStorageCatalog() + path := new(catalogItemReq) + + var err error = nil + var statusCode int = 200 + var response models.Response = models.Response{ + Status: status.STATUS_OK, + Meta: &meta, + } + + err = c.ShouldBindUri(path) + if err != nil { + response.Info = fmt.Sprintf("Error: %s", err.Error()) + response.Status = status.STATUS_BAD_REQUEST + meta.RequestFinished = time.Now().Unix() + statusCode = 400 + c.JSON(statusCode, response) + return + } + + data, err := s.GetCatalogItemByCode(ctx, path.Code) + + response.Data = data + meta.RequestFinished = time.Now().Unix() + if data == nil { + statusCode = 404 + response.Status = status.STATUS_NOT_FOUND + } + + c.JSON(statusCode, response) } diff --git a/handlers/catalog/endpoints/ep.go b/handlers/catalog/endpoints/ep.go index 46b73b8..8cb2605 100644 --- a/handlers/catalog/endpoints/ep.go +++ b/handlers/catalog/endpoints/ep.go @@ -7,6 +7,7 @@ type handlers struct{} type Handlers interface { GetFilters(c *gin.Context) GetCatalogItems(c *gin.Context) + GetCatalogItem(c *gin.Context) } func GetHandlers() Handlers { diff --git a/handlers/catalog/routes.go b/handlers/catalog/routes.go index 5dd0fac..aa53102 100644 --- a/handlers/catalog/routes.go +++ b/handlers/catalog/routes.go @@ -1,12 +1,13 @@ 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" @@ -22,9 +23,11 @@ func HandleRoutes(parent *gin.RouterGroup) { // Caching for production usage catalog.GET("", cache.CachePage(cacheStore, 15*time.Minute, h.GetCatalogItems)) catalog.GET("/filters", cache.CachePage(cacheStore, 15*time.Minute, h.GetFilters)) + catalog.GET("/:code", cache.CachePage(cacheStore, 15*time.Minute, h.GetCatalogItem)) } catalog.GET("", h.GetCatalogItems) + catalog.GET("/:code", h.GetCatalogItem) catalog.GET("/filters", h.GetFilters) // catalog.GET("/filters", cache.CachePage(cacheStore, 15, h.GetFilters)) diff --git a/handlers/news/endpoints/news.go b/handlers/news/endpoints/news.go new file mode 100644 index 0000000..c6c7ee2 --- /dev/null +++ b/handlers/news/endpoints/news.go @@ -0,0 +1,113 @@ +package endpoints + +import ( + "context" + "fmt" + "relynolli-server/models" + "relynolli-server/status" + "relynolli-server/storage" + "time" + + "github.com/gin-gonic/gin" +) + +type handlers struct{} + +type Handlers interface { + GetNews(c *gin.Context) + RetrieveNews(c *gin.Context) +} + +func GetHandlers() Handlers { + return &handlers{} +} + +type ListNewsRequest struct { + Limit int `form:"limit" ` + Page int `form:"page"` +} + +func (h *handlers) GetNews(c *gin.Context) { + ctx := context.Background() + meta := models.Meta{ + RequestStarted: time.Now().Unix(), + } + + query := ListNewsRequest{ + Limit: 10, + Page: 1, + } + + err := c.ShouldBindQuery(&query) + if err != nil { + meta.RequestFinished = time.Now().Unix() + c.JSON(400, models.Response{ + Status: status.STATUS_BAD_REQUEST, + Info: fmt.Sprintf("Error: %s", err.Error()), + Meta: &meta, + }) + return + } + + s := storage.NewStorageNews() + count, resp, err := s.GetNews(ctx, int64(query.Limit), int64(query.Limit * (query.Page - 1))) + if err != nil { + meta.RequestFinished = time.Now().Unix() + c.JSON(500, models.Response{ + Status: status.STATUS_SERVER_ERROR, + Info: fmt.Sprintf("Error: %s", err.Error()), + Meta: &meta, + }) + return + } + + meta.Count = count + meta.Limit = query.Limit + meta.Page = query.Page + c.JSON(200, models.Response{ + Status: status.STATUS_OK, + Data: resp, + Meta: &meta, + }) + +} + +type retireveNewsReq struct { + Code string `uri:"code" binding:"required"` +} + +func (h *handlers) RetrieveNews(c *gin.Context) { + ctx := context.Background() + meta := models.Meta { + RequestStarted: time.Now().Unix(), + } + query := new(retireveNewsReq) + + err := c.ShouldBindUri(query) + if err != nil { + meta.RequestFinished = time.Now().Unix() + c.JSON(400, models.Response{ + Status: status.STATUS_BAD_REQUEST, + Info: fmt.Sprintf("Error: %s", err.Error()), + Meta: &meta, + }) + return + } + + s := storage.NewStorageNews() + resp, _ := s.RetrieveNews(ctx, query.Code) + meta.RequestFinished = time.Now().Unix() + + statusResult := status.STATUS_OK + responseCode := 200 + if resp == nil { + statusResult = status.STATUS_NOT_FOUND + responseCode = 404 + } + + c.JSON(responseCode, models.Response{ + Status: statusResult, + Data: resp, + Meta: &meta, + }) +} diff --git a/handlers/news/routes.go b/handlers/news/routes.go new file mode 100644 index 0000000..a9b2092 --- /dev/null +++ b/handlers/news/routes.go @@ -0,0 +1,26 @@ +package news + +import ( + "os" + "relynolli-server/handlers/news/endpoints" + "relynolli-server/internal" + "time" + + "github.com/gin-contrib/cache" + "github.com/gin-gonic/gin" +) + +func HandleRoutes(parent *gin.RouterGroup) { + h := endpoints.GetHandlers() + cacheStore := internal.InitCacheStore() + catalog := parent.Group("/news") + if os.Getenv("IS_PROD") == "1" { + // Caching for production usage + catalog.GET("", cache.CachePage(cacheStore, 15*time.Minute, h.GetNews)) + catalog.GET("/:code", cache.CachePage(cacheStore, 15*time.Minute, h.RetrieveNews)) + } + + catalog.GET("", h.GetNews) + catalog.GET("/:code", h.RetrieveNews) + +} diff --git a/handlers/routers.go b/handlers/routers.go index 1a1f129..f4cad64 100644 --- a/handlers/routers.go +++ b/handlers/routers.go @@ -1,7 +1,11 @@ package handlers import ( + "relynolli-server/handlers/cart" + "relynolli-server/handlers/news" + "github.com/gin-gonic/gin" + // "relynolli-server/handlers/cart" "relynolli-server/handlers/catalog" // "relynolli-server/handlers/order" @@ -11,7 +15,8 @@ import ( func InitializeRouter(router *gin.Engine) { APIV1Router := router.Group("/api/v1") catalog.HandleRoutes(APIV1Router) - // cart.HandleRoutes(APIV1Router) + cart.HandleRoutes(APIV1Router) + news.HandleRoutes(APIV1Router) // order.HandleRoutes(APIV1Router) // validate.HandleRoutes(APIV1Router) } diff --git a/internal/redis.go b/internal/redis.go index 2884ab8..286be02 100644 --- a/internal/redis.go +++ b/internal/redis.go @@ -13,20 +13,20 @@ import ( var ( redisInstance *redis.Client = nil - cacheStore *persistence.RedisStore + cacheStore *persistence.RedisStore ) type Cache interface { } -func InitRedis() (*redis.Client) { +func InitRedis() *redis.Client { if redisInstance == nil { redis_db_num, err := strconv.Atoi(os.Getenv("REDIS_DATABASE")) if err != nil { log.Fatalln("REDIS_DATABASE should be integer") } - redisInstance = redis.NewClient(&redis.Options{Addr: os.Getenv("REDIS_ADDRESS"), Password: os.Getenv("REDIS_PASSWORD"), DB: redis_db_num}) + redisInstance = redis.NewClient(&redis.Options{Addr: os.Getenv("REDIS_ADDRESS"), Password: os.Getenv("REDIS_PASSWORD"), DB: redis_db_num, Username: os.Getenv("REDIS_USERNAME")}) _, conError := redisInstance.Ping(context.Background()).Result() if conError != nil { @@ -36,9 +36,9 @@ func InitRedis() (*redis.Client) { return redisInstance } -func InitCacheStore() *persistence.RedisStore{ +func InitCacheStore() *persistence.RedisStore { if cacheStore == nil { - cacheStore = persistence.NewRedisCache(os.Getenv("REDIS_ADDRESS"), os.Getenv("REDIS_PASSWORD"), 15 * time.Minute) + cacheStore = persistence.NewRedisCache(os.Getenv("REDIS_ADDRESS"), os.Getenv("REDIS_PASSWORD"), 15*time.Minute) } return cacheStore } diff --git a/models/article/db.go b/models/article/db.go new file mode 100644 index 0000000..0a6b6d6 --- /dev/null +++ b/models/article/db.go @@ -0,0 +1 @@ +package article diff --git a/models/cart/db.go b/models/cart/db.go new file mode 100644 index 0000000..19a3f4f --- /dev/null +++ b/models/cart/db.go @@ -0,0 +1,27 @@ +package cart + +import ( + "github.com/uptrace/bun" + "relynolli-server/models/catalog" + "time" +) + +type DBFuser struct { + bun.BaseModel `bun:"table:b_sale_fuser"` + Id int64 `bun:"ID,pk" json:"id"` + Code string `bun:"CODE,default:md5(now())" json:"code" json:"code"` + UserId int64 `bun:"USER_ID,nullzero" json:"userId"` + DateInserted time.Time `bun:"DATE_INSERT" json:"dateInserted"` + DateUpdated time.Time `bun:"DATE_UPDATE" json:"dateUpdated"` +} + +type DBCart struct { + bun.BaseModel `bun:"table:api_cart"` + Id int64 `bun:"id,pk" json:"id"` + FuserId int64 `bun:"fuser_id" json:"fuserId"` + ProductId int64 `bun:"product_id" json:"productId"` + PriceTypeId int64 `bun:"price_type_id" json:"priceTypeId"` + Quantity int64 `bun:"quantity" json:"quantity"` + Fuser *DBFuser `bun:"rel:belongs-to,join:fuser_id=ID" json:"fuser"` + Product *catalog.DBCatalog `bun:"rel:belongs-to,join:product_id=id" json:"product"` +} diff --git a/models/catalog/db.go b/models/catalog/db.go index a06b8d8..2a73c84 100644 --- a/models/catalog/db.go +++ b/models/catalog/db.go @@ -4,7 +4,7 @@ import "github.com/uptrace/bun" type DBCatalog struct { bun.BaseModel `bun:"select:api_catalog"` - Id int64 `bun:"id" json:"id"` + Id int64 `bun:"id,pk" json:"id"` Code string `bun:"code" json:"code"` Name string `bun:"name" json:"name"` IsActive bool `bun:"is_active,type:integer" json:"isActive"` diff --git a/models/catalog/domain.go b/models/catalog/domain.go index d7fc61b..e571e24 100644 --- a/models/catalog/domain.go +++ b/models/catalog/domain.go @@ -1,13 +1 @@ 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/news/db.go b/models/news/db.go new file mode 100644 index 0000000..644e54d --- /dev/null +++ b/models/news/db.go @@ -0,0 +1,19 @@ +package news + +import ( + "time" + + "github.com/uptrace/bun" +) + +type DBNews struct { + bun.BaseModel `bun:"select:api_news"` + ID int64 `bun:"id" json:"id"` + IsActive bool `bun:"is_active" json:"isActive"` + Sort int64 `bun:"sort" json:"sort"` + Name string `bun:"name" json:"name"` + Content string `bun:"content" json:"content"` + Code string `bun:"code" json:"code"` + Picture string `bun:"picture" json:"picture"` + Date time.Time `bun:"date" json:"date"` +} diff --git a/services/cart.go b/services/cart.go index a918cd9..5e568ea 100644 --- a/services/cart.go +++ b/services/cart.go @@ -1,109 +1 @@ package services - -import ( - "context" - "encoding/json" - "fmt" - "relynolli-server/internal" - "relynolli-server/models" -) - -func GetCartItems(fuserId int) []models.CatalogWithQuantityWeb { - rdb := internal.InitRedis() - keys, _ := rdb.Keys(context.Background(), fmt.Sprintf("api.api_cart.%d.*", fuserId)).Result() - - result := []models.CatalogWithQuantityWeb{} - - for _, key := range keys { - str, _ := rdb.Get(context.Background(), key).Result() - item := models.CatalogWithQuantityWeb{} - json.Unmarshal([]byte(str), &item) - result = append(result, item) - } - - return result -} - -func CreateFuser() int64 { - stmt := "insert into b_sale_fuser (DATE_INSERT, DATE_UPDATE, CODE) values (now(), now(), md5(rand()));" - - db := internal.InitDatabase() - result := db.Execute(stmt) - lastInsertId, _ := result.LastInsertId() - return lastInsertId -} - -func AddItemToCart(fuserId int, productId int) { - - rdb := internal.InitRedis() - item, _ := GetCatalogItemById(productId) - - itemWithQuantity := models.CatalogWithQuantityWeb{ - Id: item.Id, - Code: item.Code, - Name: item.Name, - IsActive: item.IsActive, - Properties: item.Properties, - DetailText: item.DetailText, - Price: item.Price, - Quantity: 1, - AvailableQuantity: item.AvailableQuantity, - } - - marshaled, _ := json.Marshal(itemWithQuantity) - - err := rdb.Set(context.Background(), fmt.Sprintf("api.api_cart.%d.%d", fuserId, productId), string(marshaled), 0).Err() - if err != nil { - panic(err.Error()) - } -} - -func UpdateCartItem(fuserId int, productId int, quantity int) error { - if quantity <= 0 { - DeleteCartItem(fuserId, productId) - return nil - } - - item, _ := GetCatalogItemById(productId) - if item.AvailableQuantity < quantity { - return fmt.Errorf("Available quantity is less than requested. Available %d, requested %d", item.AvailableQuantity, quantity) - } - - itemWithQuantity := models.CatalogWithQuantityWeb{ - Id: item.Id, - Code: item.Code, - Name: item.Name, - IsActive: item.IsActive, - Properties: item.Properties, - DetailText: item.DetailText, - Price: item.Price, - Quantity: quantity, - AvailableQuantity: item.AvailableQuantity, - } - - marshaled, _ := json.Marshal(itemWithQuantity) - - rdb := internal.InitRedis() - - rdb.Set(context.Background(), fmt.Sprintf("api.api_cart.%d.%d", fuserId, productId), string(marshaled), 0) - return nil - - //var availableQunatity int - //stmtQuantity := fmt.Sprintf("select QUANTITY as q from b_catalog_product where ID = %d;", productId) - //updateStmt := fmt.Sprintf("update api_cart set quantity = %d where product_id = %d and fuser_id = %d", quantity, productId, fuserId) - - //db := internal.InitDatabase() - //rows := db.Query(stmtQuantity) - //rows.Next() - //rows.Scan(&availableQunatity) - //if quantity > availableQunatity { - // return fmt.Errorf("Available quantity is less than requested. Available %d, requested %d", availableQunatity, quantity) - //} - //db.Execute(updateStmt) - //return nil -} - -func DeleteCartItem(fuserId int, productId int) { - rdb := internal.InitRedis() - rdb.Del(context.Background(), fmt.Sprintf("api.api_cart.%d.%d", fuserId, productId)).Err() -} diff --git a/services/catalog.go b/services/catalog.go deleted file mode 100644 index 6f1522e..0000000 --- a/services/catalog.go +++ /dev/null @@ -1,108 +0,0 @@ -package services - -import ( - "encoding/json" - "fmt" - "relynolli-server/internal" - "relynolli-server/models" - "strings" -) - -func retrieveItems(stmt string, structure interface{}) { - db := internal.InitDatabase() - db.FetchRows(stmt, structure) -} - -func retrieveCatalogItems(stmt string) []models.CatalogStructWeb { - var catalogList []models.CatalogStruct - var returnedList []models.CatalogStructWeb - - retrieveItems(stmt, &catalogList) - for _, item := range catalogList { - itemProd := models.CatalogStructWeb{ - Id: item.Id, - Code: item.Code, - Name: item.Name, - IsActive: item.IsActive, - DetailText: item.DetailText, - AvailableQuantity: item.AvailableQuantity, - } - json.Unmarshal(item.Price, &itemProd.Price) - json.Unmarshal(item.Properties, &itemProd.Properties) - returnedList = append(returnedList, itemProd) - } - return returnedList -} - -func GetCatalogItemsCount() int { - stmt := "select count(id) from api_catalog where available_quantity > 0 and is_active = 1;" - var count int - db := internal.InitDatabase() - rows := db.Query(stmt) - rows.Next() - rows.Scan(&count) - return count -} - -func GetCatalogItems(limit int, offset int) []models.CatalogStructWeb { - stmt := fmt.Sprintf("select * from api_catalog where available_quantity > 0 and is_active = 1 order by code limit %d offset %d;", limit, offset) - return retrieveCatalogItems(stmt) -} - -func GetCatalogItem(code string) (models.CatalogStructWeb, error) { - stmt := fmt.Sprintf("select * from api_catalog where code = '%s';", code) - items := retrieveCatalogItems(stmt) - if len(items) == 0 { - return models.CatalogStructWeb{}, fmt.Errorf("Not founded catalog item with given code") - } - return retrieveCatalogItems(stmt)[0], nil -} - -func GetCatalogItemById(id int) (models.CatalogStructWeb, error) { - stmt := fmt.Sprintf("select * from api_catalog where id = %d;", id) - items := retrieveCatalogItems(stmt) - if len(items) == 0 { - return models.CatalogStructWeb{}, fmt.Errorf("Not founded catalog item with given code") - } - return retrieveCatalogItems(stmt)[0], nil -} - -func FilterCatalogItems(filters map[string][]string, limit int, offset int) []models.CatalogStructWeb { - // Generate stmt - propertiesSubStmt := "properties->>'$.%s' = '%s'" - - stmt := "select * from api_catalog where %s" - - sample := "(%s)" - - filterList := [][]string{} - - for key, filter := range filters { - if key == "isFilter" { - continue - } - if key == "limit" { - continue - } - if key == "page" { - continue - } - - subFilterArr := []string{} - values := strings.Split(filter[0], ",") - for _, val := range values { - subFilterArr = append(subFilterArr, fmt.Sprintf(propertiesSubStmt, key, val)) - } - filterList = append(filterList, subFilterArr) - } - - samples := []string{} - - for _, arr := range filterList { - samples = append(samples, fmt.Sprintf(sample, strings.Join(arr, " or "))) - } - - stmt = fmt.Sprintf(stmt, strings.Join(samples, " and ")) - print("\n" + stmt + "\n") - return retrieveCatalogItems(stmt + fmt.Sprintf("and is_active = 1 and available_quantity > 0;")) -} diff --git a/services/order.go b/services/order.go index 352469b..47d0749 100644 --- a/services/order.go +++ b/services/order.go @@ -1,147 +1,131 @@ package services -import ( - "context" - "encoding/json" - "fmt" - "relynolli-server/external/bitrix" - "relynolli-server/external/kassa" - "relynolli-server/external/kassa/Measure" - "relynolli-server/external/kassa/PaymentMode" - "relynolli-server/external/kassa/PaymentSubject" - "relynolli-server/external/kassa/VatCodes" - "relynolli-server/internal" - "relynolli-server/models" - "strconv" - "strings" -) - -func GetTotal(fuserId int) float64 { - rdb := internal.InitRedis() - keys, _ := rdb.Keys(context.Background(), fmt.Sprintf("api.api_cart.%d.*", fuserId)).Result() - - result := []models.CatalogWithQuantityWeb{} - - for _, key := range keys { - str, _ := rdb.Get(context.Background(), key).Result() - item := models.CatalogWithQuantityWeb{} - - json.Unmarshal([]byte(str), &item) - result = append(result, item) - } - - sum := float64(0) - - for _, catalogItem := range result { - sum = sum + catalogItem.Price["BASE"].(float64)*float64(catalogItem.Quantity) - } - - return sum -} - -type addProductsToOrderReq struct { - ProductId int `db:"product_id"` - PriceTypeId int `db:"price_type_id"` - Quantity int `db:"quantity"` - Price float64 `db:"price"` -} - -func addProductsToOrder(api bitrix.Bitrix, fuserId int, orderId int) error { - //Получаем данные из корзины - - cartItems := GetCartItems(fuserId) - - rdb := internal.InitRedis() - rdb.Keys(context.Background(), "") - - for _, product := range cartItems { - err := api.AddProductToOrder(orderId, product.Id, product.Price["BASE"].(float64), product.Quantity) - if err != nil { - return err - } - } - return nil - -} - -func MakeOrder(fuserId int, email string, fullName string, phone string) (map[string]interface{}, error) { - - // Инициализируем api - - api := bitrix.Initialize() - - // 1. Создаем анонимного пользователя - - userId, _ := api.CreateAnonymousUser() - - // 2. Создаем заказ - orderId, _ := api.CreateOrder(userId) - - // --- обновляем контакт пользователя - order, orderErr := api.GetOrderInfo(orderId) - if orderErr != nil { - return nil, orderErr - } - clientId, _ := strconv.Atoi(order.Clients[0].EntityId) - api.UpdateContact(clientId, email, fullName, phone) - - // 3. Добавляем элементы в корзину - addProductErr := addProductsToOrder(api, fuserId, orderId) - if addProductErr != nil { - return nil, addProductErr - } - - // 4. Получаем обновленный ресурс заказа - order, _ = api.GetOrderInfo(orderId) - - // 5. Добавляем способ оплаты товара - createPaymentError := api.CreatePayment(orderId, order.Price) - - if createPaymentError != nil { - return nil, createPaymentError - } - - // 6. Получаем ресурс оплаты и url для нее - paymentData, _ := kassa.CreatePayment(orderId, order.Price, fullName, email, phone, getItemsForPayment(order)) - - insPaymentDataStmt := fmt.Sprintf(` - insert into api_youkassa_payment (payment_id, order_id, link, status) - values ('%s', - '%s', - '%s', - '%s'); - `, paymentData["id"].(string), - orderId, - paymentData["confirmation"].(map[string]interface{})["confirmation_url"].(string), - paymentData["status"].(string), - ) - - db := internal.InitDatabase() - - db.Execute(insPaymentDataStmt) - - return paymentData, nil -} - -func getItemsForPayment(order *bitrix.OrderResource) []kassa.KassaReceiptItems { - result := []kassa.KassaReceiptItems{} - - for _, basketItem := range order.BasketItems { - quantity, _ := strconv.Atoi(strings.Split(basketItem.Quantity, ".")[0]) - item := kassa.KassaReceiptItems{ - Description: basketItem.Name, - Amount: kassa.KassaAmount{ - Value: fmt.Sprintf("%f", basketItem.Price), - Currency: "RUB", - }, - VatCode: VatCodes.NDS_20, - Quantity: fmt.Sprintf("%d", quantity), - Measure: Measure.PIECE, - PaymentSubject: PaymentSubject.COMMODITY, - PaymentMode: PaymentMode.FULL_PAYMENT, - } - result = append(result, item) - } - - return result -} +//func GetTotal(fuserId int) float64 { +// rdb := internal.InitRedis() +// keys, _ := rdb.Keys(context.Background(), fmt.Sprintf("api.api_cart.%d.*", fuserId)).Result() +// +// result := []models.CatalogWithQuantityWeb{} +// +// for _, key := range keys { +// str, _ := rdb.Get(context.Background(), key).Result() +// item := models.CatalogWithQuantityWeb{} +// +// json.Unmarshal([]byte(str), &item) +// result = append(result, item) +// } +// +// sum := float64(0) +// +// for _, catalogItem := range result { +// sum = sum + catalogItem.Price["BASE"].(float64)*float64(catalogItem.Quantity) +// } +// +// return sum +//} +// +//type addProductsToOrderReq struct { +// ProductId int `db:"product_id"` +// PriceTypeId int `db:"price_type_id"` +// Quantity int `db:"quantity"` +// Price float64 `db:"price"` +//} +// +//func addProductsToOrder(api bitrix.Bitrix, fuserId int, orderId int) error { +// //Получаем данные из корзины +// +// cartItems := GetCartItems(fuserId) +// +// rdb := internal.InitRedis() +// rdb.Keys(context.Background(), "") +// +// for _, product := range cartItems { +// err := api.AddProductToOrder(orderId, product.Id, product.Price["BASE"].(float64), product.Quantity) +// if err != nil { +// return err +// } +// } +// return nil +// +//} +// +//func MakeOrder(fuserId int, email string, fullName string, phone string) (map[string]interface{}, error) { +// +// // Инициализируем api +// +// api := bitrix.Initialize() +// +// // 1. Создаем анонимного пользователя +// +// userId, _ := api.CreateAnonymousUser() +// +// // 2. Создаем заказ +// orderId, _ := api.CreateOrder(userId) +// +// // --- обновляем контакт пользователя +// order, orderErr := api.GetOrderInfo(orderId) +// if orderErr != nil { +// return nil, orderErr +// } +// clientId, _ := strconv.Atoi(order.Clients[0].EntityId) +// api.UpdateContact(clientId, email, fullName, phone) +// +// // 3. Добавляем элементы в корзину +// addProductErr := addProductsToOrder(api, fuserId, orderId) +// if addProductErr != nil { +// return nil, addProductErr +// } +// +// // 4. Получаем обновленный ресурс заказа +// order, _ = api.GetOrderInfo(orderId) +// +// // 5. Добавляем способ оплаты товара +// createPaymentError := api.CreatePayment(orderId, order.Price) +// +// if createPaymentError != nil { +// return nil, createPaymentError +// } +// +// // 6. Получаем ресурс оплаты и url для нее +// paymentData, _ := kassa.CreatePayment(orderId, order.Price, fullName, email, phone, getItemsForPayment(order)) +// +// insPaymentDataStmt := fmt.Sprintf(` +// insert into api_youkassa_payment (payment_id, order_id, link, status) +// values ('%s', +// '%s', +// '%s', +// '%s'); +// `, paymentData["id"].(string), +// orderId, +// paymentData["confirmation"].(map[string]interface{})["confirmation_url"].(string), +// paymentData["status"].(string), +// ) +// +// db := internal.InitDatabase() +// +// db.Execute(insPaymentDataStmt) +// +// return paymentData, nil +//} +// +//func getItemsForPayment(order *bitrix.OrderResource) []kassa.KassaReceiptItems { +// result := []kassa.KassaReceiptItems{} +// +// for _, basketItem := range order.BasketItems { +// quantity, _ := strconv.Atoi(strings.Split(basketItem.Quantity, ".")[0]) +// item := kassa.KassaReceiptItems{ +// Description: basketItem.Name, +// Amount: kassa.KassaAmount{ +// Value: fmt.Sprintf("%f", basketItem.Price), +// Currency: "RUB", +// }, +// VatCode: VatCodes.NDS_20, +// Quantity: fmt.Sprintf("%d", quantity), +// Measure: Measure.PIECE, +// PaymentSubject: PaymentSubject.COMMODITY, +// PaymentMode: PaymentMode.FULL_PAYMENT, +// } +// result = append(result, item) +// } +// +// return result +//} diff --git a/services/validate.go b/services/validate.go index a7acee5..0e8a98e 100644 --- a/services/validate.go +++ b/services/validate.go @@ -1,32 +1,27 @@ package services -import ( - "fmt" - "relynolli-server/external/bitrix" - "relynolli-server/internal" -) - -func YookassaValidate(paymentId string, status string) { - stmt := fmt.Sprintf(`select t1.order_id as order_id, t2.ID as payment_id from api_youkassa_payment t1 join b_sale_order_payment t2 on t1.order_id = t2.ORDER_ID where t1.payment_id = '%s';`, paymentId) - db := internal.InitDatabase() - rows := db.Query(stmt) - - var ( - orderId int - paymentIdBitrix int - ) - - rows.Next() - rows.Scan(&orderId, &paymentIdBitrix) - - api := bitrix.Initialize() - if status == "succeeded" { - api.ApprovePayment(paymentIdBitrix, 8) - return - } - if status == "canceled" { - api.CancelOrder(orderId) - return - } - return -} +// +//func YookassaValidate(paymentId string, status string) { +// stmt := fmt.Sprintf(`select t1.order_id as order_id, t2.ID as payment_id from api_youkassa_payment t1 join b_sale_order_payment t2 on t1.order_id = t2.ORDER_ID where t1.payment_id = '%s';`, paymentId) +// db := internal.InitDatabase() +// rows := db.Query(stmt) +// +// var ( +// orderId int +// paymentIdBitrix int +// ) +// +// rows.Next() +// rows.Scan(&orderId, &paymentIdBitrix) +// +// api := bitrix.Initialize() +// if status == "succeeded" { +// api.ApprovePayment(paymentIdBitrix, 8) +// return +// } +// if status == "canceled" { +// api.CancelOrder(orderId) +// return +// } +// return +//} diff --git a/storage/article.go b/storage/article.go new file mode 100644 index 0000000..82be054 --- /dev/null +++ b/storage/article.go @@ -0,0 +1 @@ +package storage diff --git a/storage/cart.go b/storage/cart.go index a672af2..ea660ed 100644 --- a/storage/cart.go +++ b/storage/cart.go @@ -1,15 +1,127 @@ package storage -import "context" +import ( + "context" + "crypto/md5" + "encoding/hex" + "fmt" + "relynolli-server/internal" + "relynolli-server/models/cart" + "time" +) type StorageCart interface { - + CreateFuser(ctx context.Context) (int64, *cart.DBFuser, error) + GetCartItems(ctx context.Context, fuserId int64) (*[]cart.DBCart, error) + GetCartItem(ctx context.Context, fuserId, productId int64) (*cart.DBCart, error) + AddItemToCart(ctx context.Context, fuserId, productId int64) error + UpdateCartItem(ctx context.Context, fuserId, productId, quantity int64) error + DeleteCartItem(ctx context.Context, fuserId, productId int64) error } -func (s *storage) GetCartItem(ctx context.Context) { - +func NewStorageCart() StorageCart { + if instance == nil { + instance = &storage{ + db: internal.InitDatabase().GetInstance(), + rdb: internal.InitRedis(), + } + } + return instance } -func (s *storage) GetCartItems(ctx context.Context) { +func (s *storage) CreateFuser(ctx context.Context) (int64, *cart.DBFuser, error) { + //stmt := "insert into b_sale_fuser (DATE_INSERT, DATE_UPDATE, CODE) values (now(), now(), md5(rand()));" + hash := md5.Sum([]byte(fmt.Sprintf("%d", time.Now().Unix()))) + model := &cart.DBFuser{ + Code: hex.EncodeToString(hash[:]), + DateInserted: time.Now().UTC(), + DateUpdated: time.Now().UTC(), + } + + res, err := s.db.NewInsert().Model(model).Exec(ctx) + id, _ := res.LastInsertId() + + s.db.NewSelect().Model(model).Where("id = ?", id).Scan(ctx) + + if err != nil { + return 0, nil, err + } + return model.Id, model, nil +} + +func (s *storage) GetCartItems(ctx context.Context, fuserId int64) (*[]cart.DBCart, error) { + result := new([]cart.DBCart) + err := s.db.NewSelect().Model(result).Relation("Product").Relation("Fuser").Where("fuser_id = ?", fuserId).Scan(ctx) + + if err != nil { + return nil, err + } + return result, nil +} + +func (s *storage) GetCartItem(ctx context.Context, fuserId, productId int64) (*cart.DBCart, error) { + result := new(cart.DBCart) + err := s.db.NewSelect().Model(result).Relation("Product").Relation("Fuser").Where("fuser_id = ?", fuserId).Where("product_id = ?", productId).Scan(ctx) + + if err != nil { + return nil, err + } + return result, nil +} + +func (s *storage) AddItemToCart(ctx context.Context, fuserId, productId int64) error { + + item, _ := s.GetCatalogItem(ctx, &productId) + isExists, err := s.db.NewSelect().Model((*cart.DBCart)(nil)).Where("fuser_id = ?", fuserId).Where("product_id = ?", productId).Exists(ctx) + + if isExists || item.AvailableQuantity < 1 { + return nil + } + + if err != nil { + panic(err.Error()) + } + + newItem := &cart.DBCart{ + FuserId: fuserId, + ProductId: productId, + PriceTypeId: 1, + Quantity: 1, + } + + _, err = s.db.NewInsert().Model(newItem).Exec(ctx) + + if err != nil { + return nil + } + + return nil +} + +func (s *storage) UpdateCartItem(ctx context.Context, fuserId, productId, quantity int64) error { + if quantity <= 0 { + // TODO Implement + return nil + } + + item, _ := s.GetCartItem(ctx, fuserId, productId) + + if item.Product.AvailableQuantity < quantity { + return fmt.Errorf("Available quantity is less than requested. Available %d, requested %d", item.Product.AvailableQuantity, quantity) + } + + item.Quantity = quantity + + s.db.NewUpdate().Model(item).Where("id = ?", item.Id).Exec(ctx) + + return nil +} + +func (s *storage) DeleteCartItem(ctx context.Context, fuserId, productId int64) error { + _, err := s.db.NewDelete().Model((*cart.DBCart)(nil)).Where("fuser_id = ?", fuserId).Where("product_id = ?", productId).Exec(ctx) + if err != nil { + return err + } + return nil } diff --git a/storage/catalog.go b/storage/catalog.go index 2fca1a1..65e82e6 100644 --- a/storage/catalog.go +++ b/storage/catalog.go @@ -14,6 +14,8 @@ import ( type StorageCatalog interface { GetCatalogItem(ctx context.Context, id *int64) (*catalog.DBCatalog, error) + GetCatalogItemByCode(ctx context.Context, code string) (*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) } @@ -21,17 +23,17 @@ type StorageCatalog interface { func NewStorageCatalog() StorageCatalog { if instance == nil { instance = &storage{ - db: internal.InitDatabase().GetInstance(), + db: internal.InitDatabase().GetInstance(), + rdb: internal.InitRedis(), } } return instance } -func (s *storage) GetCatalogItem(ctx context.Context, id *int64) (*catalog.DBCatalog, error) { - db := internal.InitDatabase().GetInstance() +func (s *storage) GetCatalogItemByCode(ctx context.Context, code string) (*catalog.DBCatalog, error) { model := new(catalog.DBCatalog) - err := db.NewSelect().Model(model).Where("id = ?", id).Where("available_quantity > 0").Where("is_available = 1").Scan(ctx) + err := s.db.NewSelect().Model(model).Where("code = ?", code).Where("available_quantity > 0").Where("is_active = 1").Scan(ctx) if err != nil { return nil, err @@ -39,12 +41,22 @@ func (s *storage) GetCatalogItem(ctx context.Context, id *int64) (*catalog.DBCat return model, nil } -func buildFilterGroup(ctx context.Context, q *bun.SelectQuery, filters *cmap.ConcurrentMap[string, []string]) *bun.SelectQuery { - db := internal.InitDatabase().GetInstance() +func (s *storage) GetCatalogItem(ctx context.Context, id *int64) (*catalog.DBCatalog, error) { + model := new(catalog.DBCatalog) + + err := s.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 (s *storage) buildFilterGroup(ctx context.Context, q *bun.SelectQuery, filters *cmap.ConcurrentMap[string, []string]) *bun.SelectQuery { availableFilters := new([]filters2.DBFilter) //Get filters - db.NewSelect().Model(availableFilters).Scan(ctx) + s.db.NewSelect().Model(availableFilters).Scan(ctx) for _, filter := range filters.Keys() { filter = filter[0 : len(filter)-2] @@ -66,18 +78,16 @@ func buildFilterGroup(ctx context.Context, q *bun.SelectQuery, filters *cmap.Con } 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).Order("code").ScanAndCount(ctx) + filterQuery := s.db.NewSelect().Model(model).Where("is_active = 1").Where("available_quantity > 0") + count, _ := s.buildFilterGroup(ctx, filterQuery, &filters).Limit(limit).Offset(offset).Order("code").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) + count, err := s.db.NewSelect().Model(models).ScanAndCount(ctx) if err != nil { return 0, nil, err } diff --git a/storage/news.go b/storage/news.go new file mode 100644 index 0000000..a197ace --- /dev/null +++ b/storage/news.go @@ -0,0 +1,45 @@ +package storage + +import ( + "context" + "relynolli-server/internal" + "relynolli-server/models/news" +) + +type StorageNews interface { + GetNews (ctx context.Context, limit, offset int64) (int, *[]news.DBNews, error) + RetrieveNews(ctx context.Context, code string) (*news.DBNews, error) +} + + +func NewStorageNews() StorageNews { + if instance == nil { + instance = &storage{ + db: internal.InitDatabase().GetInstance(), + rdb: internal.InitRedis(), + } + } + return instance +} + +func (s *storage) GetNews (ctx context.Context, limit, offset int64) (int, *[]news.DBNews, error) { + model := new([]news.DBNews) + stmt := s.db.NewSelect().Model(model).Where("is_active = 1").OrderExpr("sort ASC, date DESC").Limit(int(limit)).Offset(int(offset)) + count, err := stmt.ScanAndCount(ctx) + if err != nil { + return 0, nil, err + } + + return count, model, nil +} + + +func (s *storage) RetrieveNews(ctx context.Context, code string) (*news.DBNews, error) { + model := new(news.DBNews) + stmt := s.db.NewSelect().Model(model).Where("code = ?", code).Where("is_active = 1") + err := stmt.Scan(ctx) + if err != nil { + return nil, err + } + return model, nil +} diff --git a/storage/storage.go b/storage/storage.go index 03d6e70..947b75b 100644 --- a/storage/storage.go +++ b/storage/storage.go @@ -1,11 +1,13 @@ package storage import ( + "github.com/redis/go-redis/v9" "github.com/uptrace/bun" ) type storage struct { - db *bun.DB + db *bun.DB + rdb *redis.Client } var instance *storage = nil