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 // skip to next if status or name is empty if row[0] == "" || row[1] == "" { continue } status := fmt.Sprintf("%v", row[0]) name := fmt.Sprintf("%v", row[1]) var productVariant string if row[2] != "" { productVariant = fmt.Sprintf("%v", row[2]) } var productCharacteristics string if row[3] != "" { productCharacteristics = fmt.Sprintf("%v", row[3]) } state := getStateByStatus(status) // add product to database if not exist if !isPipelineProductInDatabase(databasePipelineProducts, name) { database.DB.Create(&structs.PipelineProduct{ Name: name, Votes: 0, }) } // cache product if state == 1 { futureProducts = append(futureProducts, structs.FutureProduct{ Name: name, Votes: getVotes(databasePipelineProducts, name), // votes from database Variant: productVariant, Characteristics: productCharacteristics, }) } else if state == 2 { inWorkProducts = append(inWorkProducts, structs.InWorkProduct{ Name: name, Variant: productVariant, Characteristics: productCharacteristics, }) } else if state == 3 { var url string if row[7] != "" { url = fmt.Sprintf("%v", row[7]) } newProducts = append(newProducts, structs.NewProduct{ 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, name string) bool { for _, pipelineProduct := range pipelineProducts { if pipelineProduct.Name == name { 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.IsNameInFutureProducts(body.Name) { logger.AddSystemLog(rslogger.LogTypeWarning, "VoteProduct invalid product name provided, name: %s", body.Name) return c.SendStatus(fiber.StatusBadRequest) } operator := "+" if query.T == "d" { operator = "-" } database.DB.Model(&structs.PipelineProduct{}).Where("name = ?", body.Name).Update("votes", gorm.Expr(fmt.Sprintf("votes %s ?", operator), 1)) cache.VoteFutureProduct(body.Name, query.T == "u") cache.SortFutureProducts() logger.AddSystemLog(rslogger.LogTypeInfo, "Vote name: %v up vote: %v", body.Name, query.T) return c.SendStatus(fiber.StatusOK) }