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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

@ -19,14 +19,14 @@ var (
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 {

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 {
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"`

View File

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

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

View File

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

1
storage/article.go Normal file
View File

@ -0,0 +1 @@
package storage

View File

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

View File

@ -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)
}
@ -22,16 +24,16 @@ func NewStorageCatalog() StorageCatalog {
if instance == nil {
instance = &storage{
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
}

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
import (
"github.com/redis/go-redis/v9"
"github.com/uptrace/bun"
)
type storage struct {
db *bun.DB
rdb *redis.Client
}
var instance *storage = nil