324 lines
8.7 KiB
Go
324 lines
8.7 KiB
Go
package productpipeline
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"jannex/admin-dashboard-backend/modules/cache"
|
|
"jannex/admin-dashboard-backend/modules/config"
|
|
"jannex/admin-dashboard-backend/modules/database"
|
|
"jannex/admin-dashboard-backend/modules/logger"
|
|
"jannex/admin-dashboard-backend/modules/notification"
|
|
"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,
|
|
Votes: 0,
|
|
})
|
|
}
|
|
|
|
// cache product
|
|
|
|
if state == 1 {
|
|
futureProducts = append(futureProducts, structs.FutureProduct{
|
|
Id: productId,
|
|
Name: name,
|
|
Votes: getVotes(databasePipelineProducts, productId), // 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
|
|
var publishedAt string
|
|
|
|
if len(row) > 8 && row[8] != "" {
|
|
url = fmt.Sprintf("%v", row[8])
|
|
} else {
|
|
logger.AddSystemLog(rslogger.LogTypeWarning, "Url missing for product id: %s name: %s", productId, name)
|
|
|
|
notification.AddNotification(nil, structs.AddNotificationRequest{
|
|
UserIds: config.Cfg.NotificationUserIds,
|
|
Type: 3,
|
|
Title: "Missing 'Shop Produkt Link' for product pipeline\n\nproduct id: " + productId + "\nname: " + name + "\n\nDie URL für das Produkt in dem Google Sheet 'Produkte' -> Worksheet: 'Pipeline' eintragen!",
|
|
})
|
|
}
|
|
|
|
if len(row) > 9 && row[9] != "" {
|
|
publishedAt = fmt.Sprintf("%v", row[9])
|
|
}
|
|
|
|
newProducts = append(newProducts, structs.NewProduct{
|
|
Id: productId,
|
|
Name: name,
|
|
Url: url,
|
|
Variant: productVariant,
|
|
Characteristics: productCharacteristics,
|
|
PublishedAt: publishedAt,
|
|
})
|
|
}
|
|
}
|
|
|
|
cache.SetPipelineProductsCache(structs.PipelineProductsCache{
|
|
NewProducts: newProducts,
|
|
InWorkProducts: inWorkProducts,
|
|
FutureProducts: futureProducts,
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
func getVotes(pipelineProducts []structs.PipelineProduct, id string) int {
|
|
for _, pipelineProduct := range pipelineProducts {
|
|
if pipelineProduct.Id == id {
|
|
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)
|
|
}
|
|
|
|
// can be triggered by admin dashboard group task
|
|
func GetManualFetchProducts(c *fiber.Ctx) error {
|
|
logger.AddSystemLog(rslogger.LogTypeInfo, "Request for manual retrieval of Google Sheet products received")
|
|
|
|
if err := FetchGoogleSheets(); err != nil {
|
|
return c.SendStatus(fiber.StatusInternalServerError)
|
|
}
|
|
|
|
return c.SendStatus(fiber.StatusOK)
|
|
}
|