Files
b2b/app/service/productDescriptionService/productDescriptionService.go
2026-03-12 12:20:18 +01:00

295 lines
7.6 KiB
Go

package productDescriptionService
import (
"encoding/xml"
"fmt"
"io"
"slices"
"strings"
"git.ma-al.com/goc_daniel/b2b/app/db"
"git.ma-al.com/goc_daniel/b2b/app/model"
"git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors"
"gorm.io/gorm"
)
type ProductDescriptionService struct {
db *gorm.DB
}
func New() *ProductDescriptionService {
return &ProductDescriptionService{
db: db.Get(),
}
}
func isValidXHTML(s string) bool {
decoder := xml.NewDecoder(strings.NewReader(s))
hasStartTag := false
for {
tok, err := decoder.Token()
if err != nil {
if err == io.EOF {
return hasStartTag
}
return false
}
if _, ok := tok.(xml.StartElement); ok {
hasStartTag = true
}
}
}
// We assume that any user has access to all product descriptions
func (s *ProductDescriptionService) GetProductDescription(userID uint, productID uint, productShopID uint, productLangID uint) (*model.ProductDescription, error) {
var ProductDescription model.ProductDescription
err := s.db.
Table("ps_product_lang").
Where("id_product = ? AND id_shop = ? AND id_lang = ?", productID, productShopID, productLangID).
First(&ProductDescription).Error
if err != nil {
return nil, fmt.Errorf("database error: %w", err)
}
return &ProductDescription, nil
}
// Updates relevant fields with the "updates" map
func (s *ProductDescriptionService) SaveProductDescription(userID uint, productID uint, productShopID uint, productLangID uint, updates map[string]string) error {
// only some fields can be affected
allowedFields := []string{"description", "description_short", "meta_description", "meta_title", "name", "available_now", "available_later", "usage"}
for key := range updates {
if !slices.Contains(allowedFields, key) {
return responseErrors.ErrBadField
}
}
// check that fields description, description_short and usage, if they exist, have a valid html format
mustBeHTML := []string{"description", "description_short", "usage"}
for i := 0; i < len(mustBeHTML); i++ {
if text, exists := updates[mustBeHTML[i]]; exists {
if !isValidXHTML(text) {
return responseErrors.ErrInvalidHTML
}
}
}
record := model.ProductDescription{
ProductID: productID,
ShopID: productShopID,
LangID: productLangID,
}
err := s.db.
Table("ps_product_lang").
Where("id_product = ? AND id_shop = ? AND id_lang = ?", productID, productShopID, productLangID).
FirstOrCreate(&record).Error
if err != nil {
return fmt.Errorf("database error: %w", err)
}
if len(updates) == 0 {
return nil
}
updatesIface := make(map[string]interface{}, len(updates))
for k, v := range updates {
updatesIface[k] = v
}
err = s.db.
Table("ps_product_lang").
Where("id_product = ? AND id_shop = ? AND id_lang = ?", productID, productShopID, productLangID).
Updates(updatesIface).Error
if err != nil {
return fmt.Errorf("database error: %w", err)
}
return nil
}
// func (s *ProductDescriptionService) GetRepositoriesForUser(userID uint) ([]uint, error) {
// var repoIDs []uint
// err := s.db.
// Table("customer_repo_accesses").
// Where("user_id = ?", userID).
// Pluck("repo_id", &repoIDs).Error
// if err != nil {
// return nil, fmt.Errorf("database error: %w", err)
// }
// return repoIDs, nil
// }
// func (s *RepoService) UserHasAccessToRepo(userID uint, repoID uint) (bool, error) {
// var repositories []uint
// var err error
// if repositories, err = s.GetRepositoriesForUser(userID); err != nil {
// return false, err
// }
// if !slices.Contains(repositories, repoID) {
// return false, responseErrors.ErrInvalidRepoID
// }
// return true, nil
// }
// // Extract all repositories assigned to user with specific id
// func (s *RepoService) GetYearsForUser(userID uint, repoID uint) ([]uint, error) {
// if ok, err := s.UserHasAccessToRepo(userID, repoID); !ok {
// return nil, err
// }
// years, err := s.GetYears(repoID)
// if err != nil {
// return nil, fmt.Errorf("database error: %w", err)
// }
// return years, nil
// }
// func (s *RepoService) GetYears(repo uint) ([]uint, error) {
// var years []uint
// query := `
// WITH bounds AS (
// SELECT
// MIN(to_timestamp(tt.created_unix)) AS min_ts,
// MAX(to_timestamp(tt.created_unix)) AS max_ts
// FROM tracked_time tt
// JOIN issue i ON i.id = tt.issue_id
// WHERE i.repo_id = ?
// AND tt.deleted = false
// )
// SELECT
// EXTRACT(YEAR FROM y.year_start)::int AS year
// FROM bounds
// CROSS JOIN LATERAL generate_series(
// date_trunc('year', min_ts),
// date_trunc('year', max_ts),
// interval '1 year'
// ) AS y(year_start)
// ORDER BY year
// `
// err := db.Get().Raw(query, repo).Find(&years).Error
// if err != nil {
// return nil, err
// }
// return years, nil
// }
// // Extract all repositories assigned to user with specific id
// func (s *RepoService) GetQuartersForUser(userID uint, repoID uint, year uint) ([]model.QuarterData, error) {
// if ok, err := s.UserHasAccessToRepo(userID, repoID); !ok {
// return nil, err
// }
// response, err := s.GetQuarters(repoID, year)
// if err != nil {
// return nil, fmt.Errorf("database error: %w", err)
// }
// return response, nil
// }
// func (s *RepoService) GetQuarters(repo uint, year uint) ([]model.QuarterData, error) {
// var quarters []model.QuarterData
// query := `
// WITH quarters AS (
// SELECT
// make_date(?::int, 1, 1) + (q * interval '3 months') AS quarter_start,
// q + 1 AS quarter
// FROM generate_series(0, 3) AS q
// ),
// data AS (
// SELECT
// EXTRACT(QUARTER FROM to_timestamp(tt.created_unix)) AS quarter,
// SUM(tt.time) / 3600 AS time
// FROM tracked_time tt
// JOIN issue i ON i.id = tt.issue_id
// JOIN repository r ON i.repo_id = r.id
// WHERE
// EXTRACT(YEAR FROM to_timestamp(tt.created_unix)) = ?
// AND r.id = ?
// AND tt.deleted = false
// GROUP BY EXTRACT(QUARTER FROM to_timestamp(tt.created_unix))
// )
// SELECT
// COALESCE(d.time, 0) AS time,
// CONCAT(EXTRACT(YEAR FROM q.quarter_start)::int, '_Q', q.quarter) AS quarter
// FROM quarters q
// LEFT JOIN data d ON d.quarter = q.quarter
// ORDER BY q.quarter
// `
// err := db.Get().
// Raw(query, year, year, repo).
// Find(&quarters).
// Error
// if err != nil {
// return nil, err
// }
// return quarters, nil
// }
// func (s *RepoService) GetIssuesForUser(
// userID uint,
// repoID uint,
// year uint,
// quarter uint,
// p pagination.Paging,
// ) (*pagination.Found[model.IssueTimeSummary], error) {
// if ok, err := s.UserHasAccessToRepo(userID, repoID); !ok {
// return nil, err
// }
// return s.GetIssues(repoID, year, quarter, p)
// }
// func (s *RepoService) GetIssues(
// repoId uint,
// year uint,
// quarter uint,
// p pagination.Paging,
// ) (*pagination.Found[model.IssueTimeSummary], error) {
// query := db.Get().
// Table("issue i").
// Select(`
// i.id AS issue_id,
// i.name AS issue_name,
// to_timestamp(i.created_unix) AS issue_created_at,
// ROUND(SUM(tt.time) / 3600.0, 2) AS total_hours_spent
// `).
// Joins(`JOIN tracked_time tt ON tt.issue_id = i.id`).
// Joins(`JOIN "user" u ON u.id = tt.user_id`).
// Where("i.repo_id = ?", repoId).
// Where(`
// EXTRACT(YEAR FROM to_timestamp(tt.created_unix)) = ?
// AND EXTRACT(QUARTER FROM to_timestamp(tt.created_unix)) = ?
// `, year, quarter).
// Group(`
// i.id,
// i.name,
// u.id,
// u.full_name
// `).
// Order("i.created_unix")
// result, err := pagination.Paginate[model.IssueTimeSummary](p, query)
// if err != nil {
// return nil, err
// }
// return &result, nil
// }