complete news and catalog

hotfix/hotfix-mysql-error
Ernest Litvinenko 2024-03-26 02:21:35 +03:00
parent 9937d8df67
commit d7b1f65805
27 changed files with 697 additions and 511 deletions

View File

View File

View File

@ -1,35 +1,75 @@
package endpoints package endpoints
import ( import (
"context"
"fmt"
"relynolli-server/models" "relynolli-server/models"
"relynolli-server/services" "relynolli-server/status"
"strconv" "relynolli-server/storage"
"time"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
type getCartItemsRequest struct {
FuserId int64 `form:"fuserId"`
}
func (h *handlers) GetCartItems(c *gin.Context) { func (h *handlers) GetCartItems(c *gin.Context) {
ctx := context.Background()
query := new(getCartItemsRequest)
meta := models.Meta{
RequestStarted: time.Now().Unix(),
}
fuserId := c.Query("fuserId") err := c.ShouldBindQuery(query)
if fuserId == "" { if err != nil || query.FuserId == 0 {
c.JSON(400, models.Response{Status: 400, Info: "\"fuserId\" should be provided"}) 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 return
} }
idx, err := strconv.Atoi(fuserId) s := storage.NewStorageCart()
if err != nil { items, _ := s.GetCartItems(ctx, query.FuserId)
c.JSON(400, models.Response{Status: 400, Info: "\"fuserId should be an integer number\""})
return
}
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) { 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, fuser, err := s.CreateFuser(ctx)
"fuserId": lastInsertId, 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,
}) })
} }

View File

@ -7,10 +7,10 @@ type handlers struct{}
type Handlers interface { type Handlers interface {
GetCartItems(c *gin.Context) GetCartItems(c *gin.Context)
CreateFUser(c *gin.Context) CreateFUser(c *gin.Context)
//
CreateCartItem(c *gin.Context) //CreateCartItem(c *gin.Context)
UpdateCartItem(c *gin.Context) //UpdateCartItem(c *gin.Context)
DeleteCartItem(c *gin.Context) //DeleteCartItem(c *gin.Context)
} }
func GetHandlers() Handlers { func GetHandlers() Handlers {

View File

@ -1,14 +1,5 @@
package endpoints package endpoints
import (
"fmt"
"net/http"
"relynolli-server/models"
"relynolli-server/services"
"github.com/gin-gonic/gin"
)
type createCartItemRequest struct { type createCartItemRequest struct {
FuserId int `json:"fuserId"` FuserId int `json:"fuserId"`
PriceTypeId int `json:"priceTypeId,omitempty"` PriceTypeId int `json:"priceTypeId,omitempty"`
@ -27,50 +18,50 @@ type deleteCartRequest struct {
ProductId int `json:"productId"` ProductId int `json:"productId"`
} }
func (h *handlers) CreateCartItem(c *gin.Context) { //func (h *handlers) CreateCartItem(c *gin.Context) {
req := createCartItemRequest{} // req := createCartItemRequest{}
err := c.ShouldBindJSON(&req) // err := c.ShouldBindJSON(&req)
//
if err != nil { // if err != nil {
c.JSON(http.StatusBadRequest, models.Response{Status: http.StatusBadRequest, Info: fmt.Sprintf("Bad request. Error info: %s", err.Error())}) // c.JSON(http.StatusBadRequest, models.Response{Status: http.StatusBadRequest, Info: fmt.Sprintf("Bad request. Error info: %s", err.Error())})
return // return
} // }
services.AddItemToCart(req.FuserId, req.ProductId) // 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)}) // 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) { //func (h *handlers) UpdateCartItem(c *gin.Context) {
req := updateCartRequest{} // req := updateCartRequest{}
err := c.ShouldBindJSON(&req) // err := c.ShouldBindJSON(&req)
//
if err != nil { // if err != nil {
c.JSON(http.StatusBadRequest, models.Response{Status: http.StatusBadRequest, Info: fmt.Sprintf("Bad request. Error info: %s", err.Error())}) // c.JSON(http.StatusBadRequest, models.Response{Status: http.StatusBadRequest, Info: fmt.Sprintf("Bad request. Error info: %s", err.Error())})
return // return
} // }
//
err = services.UpdateCartItem(req.FuserId, req.ProductId, req.Quantity) // err = services.UpdateCartItem(req.FuserId, req.ProductId, req.Quantity)
//
if err != nil { // if err != nil {
c.JSON(http.StatusBadRequest, models.Response{Status: http.StatusBadRequest, Info: fmt.Sprintf("Bad request. Error info: %s", err.Error())}) // c.JSON(http.StatusBadRequest, models.Response{Status: http.StatusBadRequest, Info: fmt.Sprintf("Bad request. Error info: %s", err.Error())})
return // return
} // }
//
c.JSON(http.StatusOK, models.Response{Status: http.StatusOK}) // c.JSON(http.StatusOK, models.Response{Status: http.StatusOK})
//
} //}
//
func (h *handlers) DeleteCartItem(c *gin.Context) { //func (h *handlers) DeleteCartItem(c *gin.Context) {
//
req := deleteCartRequest{} // req := deleteCartRequest{}
err := c.ShouldBindJSON(&req) // err := c.ShouldBindJSON(&req)
//
if err != nil { // if err != nil {
c.JSON(400, models.Response{Status: 400, Info: fmt.Sprintf("Bad request. Error info: %s", err.Error())}) // c.JSON(400, models.Response{Status: 400, Info: fmt.Sprintf("Bad request. Error info: %s", err.Error())})
return // return
} // }
//
services.DeleteCartItem(req.FuserId, req.ProductId) // services.DeleteCartItem(req.FuserId, req.ProductId)
//
c.JSON(http.StatusNoContent, models.Response{Status: http.StatusNoContent}) // c.JSON(http.StatusNoContent, models.Response{Status: http.StatusNoContent})
} //}

View File

@ -8,15 +8,15 @@ import (
func HandleRoutes(parent *gin.RouterGroup) { func HandleRoutes(parent *gin.RouterGroup) {
h := endpoints.GetHandlers() h := endpoints.GetHandlers()
cart := parent.Group("/cart") cart := parent.Group("/cart")
itemRouter := cart.Group("/item") //itemRouter := cart.Group("/item")
{ {
cart.GET("", h.GetCartItems) cart.GET("", h.GetCartItems)
cart.POST("", h.CreateFUser) cart.POST("", h.CreateFUser)
} }
{ //{
itemRouter.POST("", h.CreateCartItem) // itemRouter.POST("", h.CreateCartItem)
itemRouter.PATCH("", h.UpdateCartItem) // itemRouter.PATCH("", h.UpdateCartItem)
itemRouter.DELETE("", h.DeleteCartItem) // itemRouter.DELETE("", h.DeleteCartItem)
} //}
} }

View File

@ -3,12 +3,13 @@ package endpoints
import ( import (
"context" "context"
"fmt" "fmt"
cmap "github.com/orcaman/concurrent-map/v2"
"relynolli-server/models" "relynolli-server/models"
"relynolli-server/status" "relynolli-server/status"
"relynolli-server/storage" "relynolli-server/storage"
"time" "time"
cmap "github.com/orcaman/concurrent-map/v2"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
// "relynolli-server/models" // "relynolli-server/models"
// "relynolli-server/services" // "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)
} }

View File

@ -7,6 +7,7 @@ type handlers struct{}
type Handlers interface { type Handlers interface {
GetFilters(c *gin.Context) GetFilters(c *gin.Context)
GetCatalogItems(c *gin.Context) GetCatalogItems(c *gin.Context)
GetCatalogItem(c *gin.Context)
} }
func GetHandlers() Handlers { func GetHandlers() Handlers {

View File

@ -1,12 +1,13 @@
package catalog package catalog
import ( import (
"github.com/gin-contrib/cache"
"os" "os"
"relynolli-server/handlers/catalog/endpoints" "relynolli-server/handlers/catalog/endpoints"
"relynolli-server/internal" "relynolli-server/internal"
"time" "time"
"github.com/gin-contrib/cache"
// "relynolli-server/internal" // "relynolli-server/internal"
// "time" // "time"
@ -22,9 +23,11 @@ func HandleRoutes(parent *gin.RouterGroup) {
// Caching for production usage // Caching for production usage
catalog.GET("", cache.CachePage(cacheStore, 15*time.Minute, h.GetCatalogItems)) catalog.GET("", cache.CachePage(cacheStore, 15*time.Minute, h.GetCatalogItems))
catalog.GET("/filters", cache.CachePage(cacheStore, 15*time.Minute, h.GetFilters)) 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("", h.GetCatalogItems)
catalog.GET("/:code", h.GetCatalogItem)
catalog.GET("/filters", h.GetFilters) catalog.GET("/filters", h.GetFilters)
// catalog.GET("/filters", cache.CachePage(cacheStore, 15, h.GetFilters)) // catalog.GET("/filters", cache.CachePage(cacheStore, 15, h.GetFilters))

View File

@ -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,
})
}

26
handlers/news/routes.go Normal file
View File

@ -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)
}

View File

@ -1,7 +1,11 @@
package handlers package handlers
import ( import (
"relynolli-server/handlers/cart"
"relynolli-server/handlers/news"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
// "relynolli-server/handlers/cart" // "relynolli-server/handlers/cart"
"relynolli-server/handlers/catalog" "relynolli-server/handlers/catalog"
// "relynolli-server/handlers/order" // "relynolli-server/handlers/order"
@ -11,7 +15,8 @@ import (
func InitializeRouter(router *gin.Engine) { func InitializeRouter(router *gin.Engine) {
APIV1Router := router.Group("/api/v1") APIV1Router := router.Group("/api/v1")
catalog.HandleRoutes(APIV1Router) catalog.HandleRoutes(APIV1Router)
// cart.HandleRoutes(APIV1Router) cart.HandleRoutes(APIV1Router)
news.HandleRoutes(APIV1Router)
// order.HandleRoutes(APIV1Router) // order.HandleRoutes(APIV1Router)
// validate.HandleRoutes(APIV1Router) // validate.HandleRoutes(APIV1Router)
} }

View File

@ -19,14 +19,14 @@ var (
type Cache interface { type Cache interface {
} }
func InitRedis() (*redis.Client) { func InitRedis() *redis.Client {
if redisInstance == nil { if redisInstance == nil {
redis_db_num, err := strconv.Atoi(os.Getenv("REDIS_DATABASE")) redis_db_num, err := strconv.Atoi(os.Getenv("REDIS_DATABASE"))
if err != nil { if err != nil {
log.Fatalln("REDIS_DATABASE should be integer") 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() _, conError := redisInstance.Ping(context.Background()).Result()
if conError != nil { if conError != nil {
@ -36,9 +36,9 @@ func InitRedis() (*redis.Client) {
return redisInstance return redisInstance
} }
func InitCacheStore() *persistence.RedisStore{ func InitCacheStore() *persistence.RedisStore {
if cacheStore == nil { 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 return cacheStore
} }

1
models/article/db.go Normal file
View File

@ -0,0 +1 @@
package article

27
models/cart/db.go Normal file
View File

@ -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"`
}

View File

@ -4,7 +4,7 @@ import "github.com/uptrace/bun"
type DBCatalog struct { type DBCatalog struct {
bun.BaseModel `bun:"select:api_catalog"` 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"` Code string `bun:"code" json:"code"`
Name string `bun:"name" json:"name"` Name string `bun:"name" json:"name"`
IsActive bool `bun:"is_active,type:integer" json:"isActive"` IsActive bool `bun:"is_active,type:integer" json:"isActive"`

View File

@ -1,13 +1 @@
package catalog 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"`
}

19
models/news/db.go Normal file
View File

@ -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"`
}

View File

@ -1,109 +1 @@
package services 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()
}

View File

@ -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;"))
}

View File

@ -1,147 +1,131 @@
package services package services
import ( //func GetTotal(fuserId int) float64 {
"context" // rdb := internal.InitRedis()
"encoding/json" // keys, _ := rdb.Keys(context.Background(), fmt.Sprintf("api.api_cart.%d.*", fuserId)).Result()
"fmt" //
"relynolli-server/external/bitrix" // result := []models.CatalogWithQuantityWeb{}
"relynolli-server/external/kassa" //
"relynolli-server/external/kassa/Measure" // for _, key := range keys {
"relynolli-server/external/kassa/PaymentMode" // str, _ := rdb.Get(context.Background(), key).Result()
"relynolli-server/external/kassa/PaymentSubject" // item := models.CatalogWithQuantityWeb{}
"relynolli-server/external/kassa/VatCodes" //
"relynolli-server/internal" // json.Unmarshal([]byte(str), &item)
"relynolli-server/models" // result = append(result, item)
"strconv" // }
"strings" //
) // sum := float64(0)
//
func GetTotal(fuserId int) float64 { // for _, catalogItem := range result {
rdb := internal.InitRedis() // sum = sum + catalogItem.Price["BASE"].(float64)*float64(catalogItem.Quantity)
keys, _ := rdb.Keys(context.Background(), fmt.Sprintf("api.api_cart.%d.*", fuserId)).Result() // }
//
result := []models.CatalogWithQuantityWeb{} // return sum
//}
for _, key := range keys { //
str, _ := rdb.Get(context.Background(), key).Result() //type addProductsToOrderReq struct {
item := models.CatalogWithQuantityWeb{} // ProductId int `db:"product_id"`
// PriceTypeId int `db:"price_type_id"`
json.Unmarshal([]byte(str), &item) // Quantity int `db:"quantity"`
result = append(result, item) // Price float64 `db:"price"`
} //}
//
sum := float64(0) //func addProductsToOrder(api bitrix.Bitrix, fuserId int, orderId int) error {
// //Получаем данные из корзины
for _, catalogItem := range result { //
sum = sum + catalogItem.Price["BASE"].(float64)*float64(catalogItem.Quantity) // cartItems := GetCartItems(fuserId)
} //
// rdb := internal.InitRedis()
return sum // rdb.Keys(context.Background(), "")
} //
// for _, product := range cartItems {
type addProductsToOrderReq struct { // err := api.AddProductToOrder(orderId, product.Id, product.Price["BASE"].(float64), product.Quantity)
ProductId int `db:"product_id"` // if err != nil {
PriceTypeId int `db:"price_type_id"` // return err
Quantity int `db:"quantity"` // }
Price float64 `db:"price"` // }
} // return nil
//
func addProductsToOrder(api bitrix.Bitrix, fuserId int, orderId int) error { //}
//Получаем данные из корзины //
//func MakeOrder(fuserId int, email string, fullName string, phone string) (map[string]interface{}, error) {
cartItems := GetCartItems(fuserId) //
// // Инициализируем api
rdb := internal.InitRedis() //
rdb.Keys(context.Background(), "") // api := bitrix.Initialize()
//
for _, product := range cartItems { // // 1. Создаем анонимного пользователя
err := api.AddProductToOrder(orderId, product.Id, product.Price["BASE"].(float64), product.Quantity) //
if err != nil { // userId, _ := api.CreateAnonymousUser()
return err //
} // // 2. Создаем заказ
} // orderId, _ := api.CreateOrder(userId)
return nil //
// // --- обновляем контакт пользователя
} // order, orderErr := api.GetOrderInfo(orderId)
// if orderErr != nil {
func MakeOrder(fuserId int, email string, fullName string, phone string) (map[string]interface{}, error) { // return nil, orderErr
// }
// Инициализируем api // clientId, _ := strconv.Atoi(order.Clients[0].EntityId)
// api.UpdateContact(clientId, email, fullName, phone)
api := bitrix.Initialize() //
// // 3. Добавляем элементы в корзину
// 1. Создаем анонимного пользователя // addProductErr := addProductsToOrder(api, fuserId, orderId)
// if addProductErr != nil {
userId, _ := api.CreateAnonymousUser() // return nil, addProductErr
// }
// 2. Создаем заказ //
orderId, _ := api.CreateOrder(userId) // // 4. Получаем обновленный ресурс заказа
// order, _ = api.GetOrderInfo(orderId)
// --- обновляем контакт пользователя //
order, orderErr := api.GetOrderInfo(orderId) // // 5. Добавляем способ оплаты товара
if orderErr != nil { // createPaymentError := api.CreatePayment(orderId, order.Price)
return nil, orderErr //
} // if createPaymentError != nil {
clientId, _ := strconv.Atoi(order.Clients[0].EntityId) // return nil, createPaymentError
api.UpdateContact(clientId, email, fullName, phone) // }
//
// 3. Добавляем элементы в корзину // // 6. Получаем ресурс оплаты и url для нее
addProductErr := addProductsToOrder(api, fuserId, orderId) // paymentData, _ := kassa.CreatePayment(orderId, order.Price, fullName, email, phone, getItemsForPayment(order))
if addProductErr != nil { //
return nil, addProductErr // insPaymentDataStmt := fmt.Sprintf(`
} // insert into api_youkassa_payment (payment_id, order_id, link, status)
// values ('%s',
// 4. Получаем обновленный ресурс заказа // '%s',
order, _ = api.GetOrderInfo(orderId) // '%s',
// '%s');
// 5. Добавляем способ оплаты товара // `, paymentData["id"].(string),
createPaymentError := api.CreatePayment(orderId, order.Price) // orderId,
// paymentData["confirmation"].(map[string]interface{})["confirmation_url"].(string),
if createPaymentError != nil { // paymentData["status"].(string),
return nil, createPaymentError // )
} //
// db := internal.InitDatabase()
// 6. Получаем ресурс оплаты и url для нее //
paymentData, _ := kassa.CreatePayment(orderId, order.Price, fullName, email, phone, getItemsForPayment(order)) // db.Execute(insPaymentDataStmt)
//
insPaymentDataStmt := fmt.Sprintf(` // return paymentData, nil
insert into api_youkassa_payment (payment_id, order_id, link, status) //}
values ('%s', //
'%s', //func getItemsForPayment(order *bitrix.OrderResource) []kassa.KassaReceiptItems {
'%s', // result := []kassa.KassaReceiptItems{}
'%s'); //
`, paymentData["id"].(string), // for _, basketItem := range order.BasketItems {
orderId, // quantity, _ := strconv.Atoi(strings.Split(basketItem.Quantity, ".")[0])
paymentData["confirmation"].(map[string]interface{})["confirmation_url"].(string), // item := kassa.KassaReceiptItems{
paymentData["status"].(string), // Description: basketItem.Name,
) // Amount: kassa.KassaAmount{
// Value: fmt.Sprintf("%f", basketItem.Price),
db := internal.InitDatabase() // Currency: "RUB",
// },
db.Execute(insPaymentDataStmt) // VatCode: VatCodes.NDS_20,
// Quantity: fmt.Sprintf("%d", quantity),
return paymentData, nil // Measure: Measure.PIECE,
} // PaymentSubject: PaymentSubject.COMMODITY,
// PaymentMode: PaymentMode.FULL_PAYMENT,
func getItemsForPayment(order *bitrix.OrderResource) []kassa.KassaReceiptItems { // }
result := []kassa.KassaReceiptItems{} // result = append(result, item)
// }
for _, basketItem := range order.BasketItems { //
quantity, _ := strconv.Atoi(strings.Split(basketItem.Quantity, ".")[0]) // return result
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
}

View File

@ -1,32 +1,27 @@
package services package services
import ( //
"fmt" //func YookassaValidate(paymentId string, status string) {
"relynolli-server/external/bitrix" // 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)
"relynolli-server/internal" // db := internal.InitDatabase()
) // rows := db.Query(stmt)
//
func YookassaValidate(paymentId string, status string) { // var (
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) // orderId int
db := internal.InitDatabase() // paymentIdBitrix int
rows := db.Query(stmt) // )
//
var ( // rows.Next()
orderId int // rows.Scan(&orderId, &paymentIdBitrix)
paymentIdBitrix int //
) // api := bitrix.Initialize()
// if status == "succeeded" {
rows.Next() // api.ApprovePayment(paymentIdBitrix, 8)
rows.Scan(&orderId, &paymentIdBitrix) // return
// }
api := bitrix.Initialize() // if status == "canceled" {
if status == "succeeded" { // api.CancelOrder(orderId)
api.ApprovePayment(paymentIdBitrix, 8) // return
return // }
} // return
if status == "canceled" { //}
api.CancelOrder(orderId)
return
}
return
}

1
storage/article.go Normal file
View File

@ -0,0 +1 @@
package storage

View File

@ -1,15 +1,127 @@
package storage package storage
import "context" import (
"context"
"crypto/md5"
"encoding/hex"
"fmt"
"relynolli-server/internal"
"relynolli-server/models/cart"
"time"
)
type StorageCart interface { 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
} }

View File

@ -14,6 +14,8 @@ import (
type StorageCatalog interface { type StorageCatalog interface {
GetCatalogItem(ctx context.Context, id *int64) (*catalog.DBCatalog, error) 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) 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) GetFilters(ctx context.Context) (int, *[]filters2.DBFilter, error)
} }
@ -22,16 +24,16 @@ func NewStorageCatalog() StorageCatalog {
if instance == nil { if instance == nil {
instance = &storage{ instance = &storage{
db: internal.InitDatabase().GetInstance(), db: internal.InitDatabase().GetInstance(),
rdb: internal.InitRedis(),
} }
} }
return instance return instance
} }
func (s *storage) GetCatalogItem(ctx context.Context, id *int64) (*catalog.DBCatalog, error) { func (s *storage) GetCatalogItemByCode(ctx context.Context, code string) (*catalog.DBCatalog, error) {
db := internal.InitDatabase().GetInstance()
model := new(catalog.DBCatalog) 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 { if err != nil {
return nil, err return nil, err
@ -39,12 +41,22 @@ func (s *storage) GetCatalogItem(ctx context.Context, id *int64) (*catalog.DBCat
return model, nil return model, nil
} }
func buildFilterGroup(ctx context.Context, q *bun.SelectQuery, filters *cmap.ConcurrentMap[string, []string]) *bun.SelectQuery { func (s *storage) GetCatalogItem(ctx context.Context, id *int64) (*catalog.DBCatalog, error) {
db := internal.InitDatabase().GetInstance() 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) availableFilters := new([]filters2.DBFilter)
//Get filters //Get filters
db.NewSelect().Model(availableFilters).Scan(ctx) s.db.NewSelect().Model(availableFilters).Scan(ctx)
for _, filter := range filters.Keys() { for _, filter := range filters.Keys() {
filter = filter[0 : len(filter)-2] 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) { 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) model := new([]catalog.DBCatalog)
filterQuery := db.NewSelect().Model(model).Where("is_active = 1").Where("available_quantity > 0") filterQuery := s.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) count, _ := s.buildFilterGroup(ctx, filterQuery, &filters).Limit(limit).Offset(offset).Order("code").ScanAndCount(ctx)
return count, model, nil return count, model, nil
} }
func (s *storage) GetFilters(ctx context.Context) (int, *[]filters2.DBFilter, error) { func (s *storage) GetFilters(ctx context.Context) (int, *[]filters2.DBFilter, error) {
models := new([]filters2.DBFilter) models := new([]filters2.DBFilter)
db := internal.InitDatabase().GetInstance() count, err := s.db.NewSelect().Model(models).ScanAndCount(ctx)
count, err := db.NewSelect().Model(models).ScanAndCount(ctx)
if err != nil { if err != nil {
return 0, nil, err return 0, nil, err
} }

45
storage/news.go Normal file
View File

@ -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
}

View File

@ -1,11 +1,13 @@
package storage package storage
import ( import (
"github.com/redis/go-redis/v9"
"github.com/uptrace/bun" "github.com/uptrace/bun"
) )
type storage struct { type storage struct {
db *bun.DB db *bun.DB
rdb *redis.Client
} }
var instance *storage = nil var instance *storage = nil