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)
|
||||
|
||||
// Repo routes (restricted)
|
||||
repo := s.restricted.Group("/repo")
|
||||
restricted.RepoHandlerRoutes(repo)
|
||||
productDescription := s.restricted.Group("/product-description")
|
||||
restricted.ProductDescriptionHandlerRoutes(productDescription)
|
||||
|
||||
// // Restricted routes example
|
||||
// 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")
|
||||
ErrVerificationTokenExpired = errors.New("verification token has expired")
|
||||
|
||||
// Typed errors for data extraction
|
||||
ErrBadRepoIDAttribute = errors.New("invalid repo id attribute")
|
||||
ErrBadYearAttribute = errors.New("invalid year attribute")
|
||||
ErrBadQuarterAttribute = errors.New("invalid quarter attribute")
|
||||
ErrBadPaging = errors.New("invalid paging")
|
||||
ErrInvalidRepoID = errors.New("repo not accessible")
|
||||
// Typed errors for product description handler
|
||||
ErrBadAttribute = errors.New("bad attribute")
|
||||
)
|
||||
|
||||
// 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):
|
||||
return i18n.T_(c, "error.err_verification_token_expired")
|
||||
|
||||
case errors.Is(err, ErrBadRepoIDAttribute):
|
||||
return i18n.T_(c, "error.err_bad_repo_id_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")
|
||||
case errors.Is(err, ErrBadAttribute):
|
||||
return i18n.T_(c, "error.err_bad_attribute")
|
||||
|
||||
default:
|
||||
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, ErrVerificationTokenExpired),
|
||||
errors.Is(err, ErrInvalidPassword),
|
||||
errors.Is(err, ErrBadRepoIDAttribute),
|
||||
errors.Is(err, ErrBadYearAttribute),
|
||||
errors.Is(err, ErrBadQuarterAttribute),
|
||||
errors.Is(err, ErrBadPaging),
|
||||
errors.Is(err, ErrInvalidRepoID):
|
||||
errors.Is(err, ErrBadAttribute):
|
||||
return fiber.StatusBadRequest
|
||||
case errors.Is(err, ErrEmailExists):
|
||||
return fiber.StatusConflict
|
||||
|
||||
Reference in New Issue
Block a user