first endpoint: getting product descriptions
This commit is contained in:
81
app/delivery/web/api/restricted/productDescription.go
Normal file
81
app/delivery/web/api/restricted/productDescription.go
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
package restricted
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/config"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/service/productDescriptionService"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProductDescriptionHandler handles endpoints that receive, save and translate product descriptions.
|
||||||
|
type ProductDescriptionHandler struct {
|
||||||
|
productDescriptionService *productDescriptionService.ProductDescriptionService
|
||||||
|
config *config.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewProductDescriptionHandler creates a new ProductDescriptionHandler instance
|
||||||
|
func NewProductDescriptionHandler() *ProductDescriptionHandler {
|
||||||
|
productDescriptionService := productDescriptionService.New()
|
||||||
|
return &ProductDescriptionHandler{
|
||||||
|
productDescriptionService: productDescriptionService,
|
||||||
|
config: config.Get(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProductDescriptionRoutes registers all product description routes
|
||||||
|
func ProductDescriptionHandlerRoutes(r fiber.Router) fiber.Router {
|
||||||
|
handler := NewProductDescriptionHandler()
|
||||||
|
|
||||||
|
r.Get("/get-product-description", handler.GetProductDescription)
|
||||||
|
// r.Get("/get-years", handler.GetYears)
|
||||||
|
// r.Get("/get-quarters", handler.GetQuarters)
|
||||||
|
// r.Get("/get-issues", handler.GetIssues)
|
||||||
|
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetProductDescription returns the product description for a given product ID
|
||||||
|
func (h *ProductDescriptionHandler) GetProductDescription(c fiber.Ctx) error {
|
||||||
|
userID, ok := c.Locals("userID").(uint)
|
||||||
|
if !ok {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).JSON(fiber.Map{
|
||||||
|
"error": responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody), // possibly could return a dfifferent error
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
productID_attribute := c.Query("productID")
|
||||||
|
productID, err := strconv.Atoi(productID_attribute)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).JSON(fiber.Map{
|
||||||
|
"error": responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
productShopID_attribute := c.Query("productShopID")
|
||||||
|
productShopID, err := strconv.Atoi(productShopID_attribute)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).JSON(fiber.Map{
|
||||||
|
"error": responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
productLangID_attribute := c.Query("productLangID")
|
||||||
|
productLangID, err := strconv.Atoi(productLangID_attribute)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).JSON(fiber.Map{
|
||||||
|
"error": responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := h.productDescriptionService.GetProductDescription(userID, uint(productID), uint(productShopID), uint(productLangID))
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(err)).JSON(fiber.Map{
|
||||||
|
"error": responseErrors.GetErrorCode(c, err),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(response)
|
||||||
|
}
|
||||||
@@ -1,179 +0,0 @@
|
|||||||
package restricted
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/config"
|
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/service/repoService"
|
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/utils/pagination"
|
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors"
|
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
// RepoHandler handles endpoints asking for repository data (to create charts)
|
|
||||||
type RepoHandler struct {
|
|
||||||
repoService *repoService.RepoService
|
|
||||||
config *config.Config
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewRepoHandler creates a new RepoHandler instance
|
|
||||||
func NewRepoHandler() *RepoHandler {
|
|
||||||
repoService := repoService.New()
|
|
||||||
return &RepoHandler{
|
|
||||||
repoService: repoService,
|
|
||||||
config: config.Get(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// RepoHandlerRoutes registers all repo routes
|
|
||||||
func RepoHandlerRoutes(r fiber.Router) fiber.Router {
|
|
||||||
handler := NewRepoHandler()
|
|
||||||
|
|
||||||
r.Get("/get-repos", handler.GetRepoIDs)
|
|
||||||
r.Get("/get-years", handler.GetYears)
|
|
||||||
r.Get("/get-quarters", handler.GetQuarters)
|
|
||||||
r.Get("/get-issues", handler.GetIssues)
|
|
||||||
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *RepoHandler) GetRepoIDs(c fiber.Ctx) error {
|
|
||||||
userID, ok := c.Locals("userID").(uint)
|
|
||||||
if !ok {
|
|
||||||
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).JSON(fiber.Map{
|
|
||||||
"error": responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
response, err := h.repoService.GetRepositoriesForUser(userID)
|
|
||||||
if err != nil {
|
|
||||||
return c.Status(responseErrors.GetErrorStatus(err)).JSON(fiber.Map{
|
|
||||||
"error": responseErrors.GetErrorCode(c, err),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.JSON(response)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *RepoHandler) GetYears(c fiber.Ctx) error {
|
|
||||||
userID, ok := c.Locals("userID").(uint)
|
|
||||||
if !ok {
|
|
||||||
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).JSON(fiber.Map{
|
|
||||||
"error": responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
repoID_attribute := c.Query("repoID")
|
|
||||||
repoID, err := strconv.Atoi(repoID_attribute)
|
|
||||||
if err != nil {
|
|
||||||
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadRepoIDAttribute)).JSON(fiber.Map{
|
|
||||||
"error": responseErrors.GetErrorCode(c, responseErrors.ErrBadRepoIDAttribute),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
response, err := h.repoService.GetYearsForUser(userID, uint(repoID))
|
|
||||||
if err != nil {
|
|
||||||
return c.Status(responseErrors.GetErrorStatus(err)).JSON(fiber.Map{
|
|
||||||
"error": responseErrors.GetErrorCode(c, err),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.JSON(response)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *RepoHandler) GetQuarters(c fiber.Ctx) error {
|
|
||||||
userID, ok := c.Locals("userID").(uint)
|
|
||||||
if !ok {
|
|
||||||
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).JSON(fiber.Map{
|
|
||||||
"error": responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
repoID_attribute := c.Query("repoID")
|
|
||||||
repoID, err := strconv.Atoi(repoID_attribute)
|
|
||||||
if err != nil {
|
|
||||||
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadRepoIDAttribute)).JSON(fiber.Map{
|
|
||||||
"error": responseErrors.GetErrorCode(c, responseErrors.ErrBadRepoIDAttribute),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
year_attribute := c.Query("year")
|
|
||||||
year, err := strconv.Atoi(year_attribute)
|
|
||||||
if err != nil {
|
|
||||||
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadYearAttribute)).JSON(fiber.Map{
|
|
||||||
"error": responseErrors.GetErrorCode(c, responseErrors.ErrBadYearAttribute),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
response, err := h.repoService.GetQuartersForUser(userID, uint(repoID), uint(year))
|
|
||||||
if err != nil {
|
|
||||||
return c.Status(responseErrors.GetErrorStatus(err)).JSON(fiber.Map{
|
|
||||||
"error": responseErrors.GetErrorCode(c, err),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.JSON(response)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *RepoHandler) GetIssues(c fiber.Ctx) error {
|
|
||||||
userID, ok := c.Locals("userID").(uint)
|
|
||||||
if !ok {
|
|
||||||
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).JSON(fiber.Map{
|
|
||||||
"error": responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
repoID_attribute := c.Query("repoID")
|
|
||||||
repoID, err := strconv.Atoi(repoID_attribute)
|
|
||||||
if err != nil {
|
|
||||||
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadRepoIDAttribute)).JSON(fiber.Map{
|
|
||||||
"error": responseErrors.GetErrorCode(c, responseErrors.ErrBadRepoIDAttribute),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
year_attribute := c.Query("year")
|
|
||||||
year, err := strconv.Atoi(year_attribute)
|
|
||||||
if err != nil {
|
|
||||||
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadYearAttribute)).JSON(fiber.Map{
|
|
||||||
"error": responseErrors.GetErrorCode(c, responseErrors.ErrBadYearAttribute),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
quarter_attribute := c.Query("quarter")
|
|
||||||
quarter, err := strconv.Atoi(quarter_attribute)
|
|
||||||
if err != nil {
|
|
||||||
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadQuarterAttribute)).JSON(fiber.Map{
|
|
||||||
"error": responseErrors.GetErrorCode(c, responseErrors.ErrBadQuarterAttribute),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
page_number_attribute := c.Query("page_number")
|
|
||||||
page_number, err := strconv.Atoi(page_number_attribute)
|
|
||||||
if err != nil {
|
|
||||||
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadPaging)).JSON(fiber.Map{
|
|
||||||
"error": responseErrors.GetErrorCode(c, responseErrors.ErrBadPaging),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
elements_per_page_attribute := c.Query("elements_per_page")
|
|
||||||
elements_per_page, err := strconv.Atoi(elements_per_page_attribute)
|
|
||||||
if err != nil {
|
|
||||||
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadPaging)).JSON(fiber.Map{
|
|
||||||
"error": responseErrors.GetErrorCode(c, responseErrors.ErrBadPaging),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
var paging pagination.Paging
|
|
||||||
paging.Page = uint(page_number)
|
|
||||||
paging.Elements = uint(elements_per_page)
|
|
||||||
|
|
||||||
response, err := h.repoService.GetIssuesForUser(userID, uint(repoID), uint(year), uint(quarter), paging)
|
|
||||||
if err != nil {
|
|
||||||
return c.Status(responseErrors.GetErrorStatus(err)).JSON(fiber.Map{
|
|
||||||
"error": responseErrors.GetErrorCode(c, err),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.JSON(response)
|
|
||||||
}
|
|
||||||
@@ -90,8 +90,8 @@ func (s *Server) Setup() error {
|
|||||||
public.AuthHandlerRoutes(auth)
|
public.AuthHandlerRoutes(auth)
|
||||||
|
|
||||||
// Repo routes (restricted)
|
// Repo routes (restricted)
|
||||||
repo := s.restricted.Group("/repo")
|
productDescription := s.restricted.Group("/product-description")
|
||||||
restricted.RepoHandlerRoutes(repo)
|
restricted.ProductDescriptionHandlerRoutes(productDescription)
|
||||||
|
|
||||||
// // Restricted routes example
|
// // Restricted routes example
|
||||||
// restricted := s.api.Group("/restricted")
|
// restricted := s.api.Group("/restricted")
|
||||||
|
|||||||
20
app/model/productDescription.go
Normal file
20
app/model/productDescription.go
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
// User represents a user in the system
|
||||||
|
type ProductDescription struct {
|
||||||
|
ProductID uint `gorm:"column:id_product;primaryKey" json:"product_id"`
|
||||||
|
ShopID uint `gorm:"column:id_shop;primaryKey" json:"shop_id"`
|
||||||
|
LangID uint `gorm:"column:id_lang;primaryKey" json:"lang_id"`
|
||||||
|
Description string `gorm:"column:description;type:text" json:"description"`
|
||||||
|
DescriptionShort string `gorm:"column:description_short;type:text" json:"description_short"`
|
||||||
|
LinkRewrite string `gorm:"column:link_rewrite;type:varchar(128)" json:"link_rewrite"`
|
||||||
|
MetaDescription string `gorm:"column:meta_description;type:varchar(512)" json:"meta_description"`
|
||||||
|
MetaKeywords string `gorm:"column:meta_keywords;type:varchar(255)" json:"meta_keywords"`
|
||||||
|
MetaTitle string `gorm:"column:meta_title;type:varchar(128)" json:"meta_title"`
|
||||||
|
Name string `gorm:"column:name;type:varchar(128)" json:"name"`
|
||||||
|
AvailableNow string `gorm:"column:available_now;type:varchar(255)" json:"available_now"`
|
||||||
|
AvailableLater string `gorm:"column:available_later;type:varchar(255)" json:"available_later"`
|
||||||
|
DeliveryInStock string `gorm:"column:delivery_in_stock;type:varchar(255)" json:"delivery_in_stock"`
|
||||||
|
DeliveryOutStock string `gorm:"column:delivery_out_stock;type:varchar(255)" json:"delivery_out_stock"`
|
||||||
|
Usage string `gorm:"column:usage;type:text" json:"usage"`
|
||||||
|
}
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
package model
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/utils/pagination"
|
|
||||||
)
|
|
||||||
|
|
||||||
// LoginRequest represents the login form data
|
|
||||||
type DataRequest struct {
|
|
||||||
RepoID uint `json:"repoid" form:"repoid"`
|
|
||||||
Step uint `json:"step" form:"step"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type PageMeta struct {
|
|
||||||
Title string
|
|
||||||
Description string
|
|
||||||
}
|
|
||||||
|
|
||||||
type QuarterData struct {
|
|
||||||
Time float64 `json:"time"`
|
|
||||||
Quarter string `json:"quarter"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type DayData struct {
|
|
||||||
Date string `json:"date"`
|
|
||||||
Time float64 `json:"time"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type RepositoryChartData struct {
|
|
||||||
Years []uint
|
|
||||||
Quarters []QuarterData
|
|
||||||
QuartersJSON string
|
|
||||||
Year uint
|
|
||||||
}
|
|
||||||
|
|
||||||
type TimeTrackedData struct {
|
|
||||||
RepoId uint
|
|
||||||
Year uint
|
|
||||||
Quarter uint
|
|
||||||
Step string
|
|
||||||
TotalTime float64
|
|
||||||
DailyData []DayData
|
|
||||||
DailyDataJSON string
|
|
||||||
Years []uint
|
|
||||||
IssueSummaries *pagination.Found[IssueTimeSummary]
|
|
||||||
}
|
|
||||||
|
|
||||||
type IssueTimeSummary struct {
|
|
||||||
IssueID uint `gorm:"column:issue_id"`
|
|
||||||
IssueName string `gorm:"column:issue_name"`
|
|
||||||
CreatedDate time.Time `gorm:"column:issue_created_at"`
|
|
||||||
TotalHoursSpent float64 `gorm:"column:total_hours_spent"`
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,219 @@
|
|||||||
|
package productDescriptionService
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/db"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/model"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ProductDescriptionService struct {
|
||||||
|
db *gorm.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func New() *ProductDescriptionService {
|
||||||
|
return &ProductDescriptionService{
|
||||||
|
db: db.Get(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
|
||||||
|
fmt.Println(userID, productID, productShopID, productLangID)
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// }
|
||||||
@@ -1,206 +0,0 @@
|
|||||||
package repoService
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"slices"
|
|
||||||
|
|
||||||
"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/pagination"
|
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
// type
|
|
||||||
type RepoService struct {
|
|
||||||
db *gorm.DB
|
|
||||||
}
|
|
||||||
|
|
||||||
func New() *RepoService {
|
|
||||||
return &RepoService{
|
|
||||||
db: db.Get(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *RepoService) 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
|
|
||||||
}
|
|
||||||
@@ -37,12 +37,8 @@ var (
|
|||||||
ErrInvalidVerificationToken = errors.New("invalid verification token")
|
ErrInvalidVerificationToken = errors.New("invalid verification token")
|
||||||
ErrVerificationTokenExpired = errors.New("verification token has expired")
|
ErrVerificationTokenExpired = errors.New("verification token has expired")
|
||||||
|
|
||||||
// Typed errors for data extraction
|
// Typed errors for product description handler
|
||||||
ErrBadRepoIDAttribute = errors.New("invalid repo id attribute")
|
ErrBadAttribute = errors.New("bad attribute")
|
||||||
ErrBadYearAttribute = errors.New("invalid year attribute")
|
|
||||||
ErrBadQuarterAttribute = errors.New("invalid quarter attribute")
|
|
||||||
ErrBadPaging = errors.New("invalid paging")
|
|
||||||
ErrInvalidRepoID = errors.New("repo not accessible")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Error represents an error with HTTP status code
|
// Error represents an error with HTTP status code
|
||||||
@@ -112,16 +108,8 @@ func GetErrorCode(c fiber.Ctx, err error) string {
|
|||||||
case errors.Is(err, ErrVerificationTokenExpired):
|
case errors.Is(err, ErrVerificationTokenExpired):
|
||||||
return i18n.T_(c, "error.err_verification_token_expired")
|
return i18n.T_(c, "error.err_verification_token_expired")
|
||||||
|
|
||||||
case errors.Is(err, ErrBadRepoIDAttribute):
|
case errors.Is(err, ErrBadAttribute):
|
||||||
return i18n.T_(c, "error.err_bad_repo_id_attribute")
|
return i18n.T_(c, "error.err_bad_attribute")
|
||||||
case errors.Is(err, ErrBadYearAttribute):
|
|
||||||
return i18n.T_(c, "error.err_bad_year_attribute")
|
|
||||||
case errors.Is(err, ErrBadQuarterAttribute):
|
|
||||||
return i18n.T_(c, "error.err_bad_quarter_attribute")
|
|
||||||
case errors.Is(err, ErrBadPaging):
|
|
||||||
return i18n.T_(c, "error.err_bad_paging")
|
|
||||||
case errors.Is(err, ErrInvalidRepoID):
|
|
||||||
return i18n.T_(c, "error.err_invalid_repo_id")
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return i18n.T_(c, "error.err_internal_server_error")
|
return i18n.T_(c, "error.err_internal_server_error")
|
||||||
@@ -152,11 +140,7 @@ func GetErrorStatus(err error) int {
|
|||||||
errors.Is(err, ErrInvalidVerificationToken),
|
errors.Is(err, ErrInvalidVerificationToken),
|
||||||
errors.Is(err, ErrVerificationTokenExpired),
|
errors.Is(err, ErrVerificationTokenExpired),
|
||||||
errors.Is(err, ErrInvalidPassword),
|
errors.Is(err, ErrInvalidPassword),
|
||||||
errors.Is(err, ErrBadRepoIDAttribute),
|
errors.Is(err, ErrBadAttribute):
|
||||||
errors.Is(err, ErrBadYearAttribute),
|
|
||||||
errors.Is(err, ErrBadQuarterAttribute),
|
|
||||||
errors.Is(err, ErrBadPaging),
|
|
||||||
errors.Is(err, ErrInvalidRepoID):
|
|
||||||
return fiber.StatusBadRequest
|
return fiber.StatusBadRequest
|
||||||
case errors.Is(err, ErrEmailExists):
|
case errors.Is(err, ErrEmailExists):
|
||||||
return fiber.StatusConflict
|
return fiber.StatusConflict
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1
|
exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1
|
||||||
Reference in New Issue
Block a user