admin-dashboard-backend/routers/router/api/v1/productpipeline/productpipeline.go

300 lines
7.8 KiB
Go

package productpipeline
import (
"context"
"errors"
"fmt"
"jannex/admin-dashboard-backend/modules/cache"
"jannex/admin-dashboard-backend/modules/database"
"jannex/admin-dashboard-backend/modules/logger"
"jannex/admin-dashboard-backend/modules/structs"
"strconv"
"git.ex.umbach.dev/Alex/roese-utils/rslogger"
"git.ex.umbach.dev/Alex/roese-utils/rsutils"
"github.com/gofiber/fiber/v2"
"google.golang.org/api/option"
"google.golang.org/api/sheets/v4"
"gorm.io/gorm"
)
var (
serviceAccountFile = "./groupTasks/secrets/shinnex-424321-b88b144bc9ef.json"
spreadsheetId = "1gZkjykb55aLWumBxHxj_ZUq_ktjGK4FlJsH_9qg-ThU"
// names of worksheets from google sheet
googleWorksheetPipeline = "Pipeline"
googleWorksheetPipelineParameterValues = "Pipeline Parameterwerte"
)
func GetProducts(c *fiber.Ctx) error {
// fetch from google sheets if cache time expired
if cache.IsPipelineProductsCacheTimeExpired() {
if err := FetchGoogleSheets(); err != nil {
return c.SendStatus(fiber.StatusInternalServerError)
}
}
return c.JSON(cache.GetPipelineProducts())
}
func getGoogleSheetsService() (*sheets.Service, error) {
ctx := context.Background()
srv, err := sheets.NewService(ctx, option.WithCredentialsFile(serviceAccountFile))
if err != nil {
logger.AddSystemLog(rslogger.LogTypeError, "Unable to retrieve Sheets client: %v", err)
return nil, err
}
return srv, nil
}
func getStateByStatus(status string) uint8 {
for _, productState := range cache.GetProductsStates() {
if productState.Status == status {
return productState.State
}
}
return 0
}
func FetchGoogleSheets() error {
srv, err := getGoogleSheetsService()
if err != nil {
logger.AddSystemLog(rslogger.LogTypeError, "Failed to fetch product states")
return err
}
if err = fetchProductStates(srv); err != nil {
logger.AddSystemLog(rslogger.LogTypeError, "FetchGoogleSheets failed on fetching product states, err: "+err.Error())
return err
}
if err = fetchProducts(srv); err != nil {
logger.AddSystemLog(rslogger.LogTypeError, "FetchGoogleSheets failed on fetching products, err: "+err.Error())
return err
}
cache.SortFutureProducts()
cache.UpdateProductsPipelineLastCacheUpdate()
return nil
}
func fetchProducts(srv *sheets.Service) error {
logger.AddSystemLog(rslogger.LogTypeInfo, "Fetching products from google sheets")
readRange := googleWorksheetPipeline
resp, err := srv.Spreadsheets.Values.Get(spreadsheetId, readRange).Do()
if err != nil {
logger.AddSystemLog(rslogger.LogTypeError, "Unable to retrieve data from sheet: %v", err)
return err
}
// no products found
if len(resp.Values) == 0 {
logger.AddSystemLog(rslogger.LogTypeInfo, "No products found")
return nil
}
// get products from database
var databasePipelineProducts []structs.PipelineProduct
database.DB.Find(&databasePipelineProducts)
// check if there is a new product on google sheets which not exists in database
var newProducts []structs.NewProduct
var inWorkProducts []structs.InWorkProduct
var futureProducts []structs.FutureProduct
for _, row := range resp.Values[1:] { // skip first google worksheet row
// maybe some error in google sheets table
if len(row) < 3 {
logger.AddSystemLog(rslogger.LogTypeError, "Skipped row, because row length less than 2")
continue
}
// skip to next if id, status or name is empty
if row[0] == "" || row[1] == "" || row[2] == "" {
continue
}
productId := fmt.Sprintf("%v", row[0])
status := fmt.Sprintf("%v", row[1])
name := fmt.Sprintf("%v", row[2])
var productVariant string
if len(row) > 3 && row[3] != "" {
productVariant = fmt.Sprintf("%v", row[3])
}
var productCharacteristics string
if len(row) > 4 && row[4] != "" {
productCharacteristics = fmt.Sprintf("%v", row[4])
}
state := getStateByStatus(status)
// add product to database if not exist
if !isPipelineProductInDatabase(databasePipelineProducts, productId) {
database.DB.Create(&structs.PipelineProduct{
Id: productId,
Name: name,
Votes: 0,
})
}
logger.AddSystemLog(rslogger.LogTypeWarning, "row %v len %v", row, len(row))
// cache product
if state == 1 {
futureProducts = append(futureProducts, structs.FutureProduct{
Id: productId,
Name: name,
Votes: getVotes(databasePipelineProducts, name), // votes from database
Variant: productVariant,
Characteristics: productCharacteristics,
})
} else if state == 2 {
inWorkProducts = append(inWorkProducts, structs.InWorkProduct{
Id: productId,
Name: name,
Variant: productVariant,
Characteristics: productCharacteristics,
})
} else if state == 3 {
var url string
if len(row) > 8 && row[8] != "" {
url = fmt.Sprintf("%v", row[8])
}
newProducts = append(newProducts, structs.NewProduct{
Id: productId,
Name: name,
Url: url,
Variant: productVariant,
Characteristics: productCharacteristics,
})
}
}
cache.SetPipelineProductsCache(structs.PipelineProductsCache{
NewProducts: newProducts,
InWorkProducts: inWorkProducts,
FutureProducts: futureProducts,
})
return nil
}
func getVotes(pipelineProducts []structs.PipelineProduct, name string) int {
for _, pipelineProduct := range pipelineProducts {
if pipelineProduct.Name == name {
return pipelineProduct.Votes
}
}
return 0
}
func fetchProductStates(srv *sheets.Service) error {
logger.AddSystemLog(rslogger.LogTypeInfo, "Fetching product states from google sheets")
readRange := googleWorksheetPipelineParameterValues
resp, err := srv.Spreadsheets.Values.Get(spreadsheetId, readRange).Do()
if err != nil {
logger.AddSystemLog(rslogger.LogTypeError, "Unable to retrieve data from sheet: %v", err)
return err
}
if len(resp.Values) == 0 {
logger.AddSystemLog(rslogger.LogTypeInfo, "No product states found")
return nil
}
var productStates []structs.ProductState
for index, row := range resp.Values {
// skip first google sheets row
if index == 0 {
continue
}
status := fmt.Sprintf("%v", row[0])
state, err := strconv.ParseUint(fmt.Sprintf("%v", row[1]), 10, 64)
if err != nil {
return errors.New("Failed to parse uint8, err: " + err.Error())
}
productStates = append(productStates, structs.ProductState{
Status: status,
State: uint8(state),
})
}
cache.SetProductStates(productStates)
return nil
}
func isPipelineProductInDatabase(pipelineProducts []structs.PipelineProduct, id string) bool {
for _, pipelineProduct := range pipelineProducts {
if pipelineProduct.Id == id {
return true
}
}
return false
}
func VoteProduct(c *fiber.Ctx) error {
var query structs.VoteProductQuery
if err := rsutils.QueryParserHelper(c, &query); err != nil {
logger.AddSystemLog(rslogger.LogTypeError, "VoteProduct invalid query, err: %s", err.Error())
return c.SendStatus(fiber.StatusBadRequest)
}
if query.T != "u" && query.T != "d" {
return c.SendStatus(fiber.StatusBadRequest)
}
var body structs.VoteProductRequest
if err := rsutils.BodyParserHelper(c, &body); err != nil {
logger.AddSystemLog(rslogger.LogTypeError, "VoteProduct invalid body, err: %s", err.Error())
return c.SendStatus(fiber.StatusBadRequest)
}
if !cache.IsIdInFutureProducts(body.Id) {
logger.AddSystemLog(rslogger.LogTypeWarning, "VoteProduct invalid product id provided, id: %s", body.Id)
return c.SendStatus(fiber.StatusBadRequest)
}
operator := "+"
if query.T == "d" {
operator = "-"
}
database.DB.Model(&structs.PipelineProduct{}).Where("id = ?", body.Id).Update("votes", gorm.Expr(fmt.Sprintf("votes %s ?", operator), 1))
cache.VoteFutureProduct(body.Id, query.T == "u")
cache.SortFutureProducts()
logger.AddSystemLog(rslogger.LogTypeInfo, "Vote name: %v up vote: %v", body.Id, query.T)
return c.SendStatus(fiber.StatusOK)
}