fix meilisearch
This commit is contained in:
@@ -585,6 +585,41 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/v1/restricted/meili-search/settings": {
|
||||||
|
"get": {
|
||||||
|
"tags": ["Search"],
|
||||||
|
"summary": "Get MeiliSearch settings",
|
||||||
|
"description": "Returns MeiliSearch configuration and settings. Requires authentication.",
|
||||||
|
"operationId": "getMeiliSearchSettings",
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"CookieAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Settings retrieved successfully",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ApiResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Not authenticated",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/v1/public/auth/me": {
|
"/api/v1/public/auth/me": {
|
||||||
"get": {
|
"get": {
|
||||||
"tags": ["Auth"],
|
"tags": ["Auth"],
|
||||||
@@ -1191,17 +1226,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/api/v1/restricted/menu/get-routes": {
|
"/api/v1/public/menu/get-routes": {
|
||||||
"get": {
|
"get": {
|
||||||
"tags": ["Menu"],
|
"tags": ["Menu"],
|
||||||
"summary": "Get routes",
|
"summary": "Get routes",
|
||||||
"description": "Returns the routing structure for the current language. Requires authentication.",
|
"description": "Returns the routing structure for the current language.",
|
||||||
"operationId": "getRoutes",
|
"operationId": "getRoutes",
|
||||||
"security": [
|
|
||||||
{
|
|
||||||
"CookieAuth": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "Routes retrieved successfully",
|
"description": "Routes retrieved successfully",
|
||||||
@@ -1222,16 +1252,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"401": {
|
|
||||||
"description": "Not authenticated",
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/Error"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
45
app/delivery/web/api/public/routing.go
Normal file
45
app/delivery/web/api/public/routing.go
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package public
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/service/menuService"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/utils/i18n"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/utils/nullable"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/utils/response"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors"
|
||||||
|
"github.com/gofiber/fiber/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RoutingHandler struct {
|
||||||
|
menuService *menuService.MenuService
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRoutingHandler() *RoutingHandler {
|
||||||
|
menuService := menuService.New()
|
||||||
|
return &RoutingHandler{
|
||||||
|
menuService: menuService,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthHandlerRoutes registers all auth routes
|
||||||
|
func RoutingHandlerRoutes(r fiber.Router) fiber.Router {
|
||||||
|
handler := NewRoutingHandler()
|
||||||
|
|
||||||
|
r.Get("/get-routes", handler.GetRouting)
|
||||||
|
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *RoutingHandler) GetRouting(c fiber.Ctx) error {
|
||||||
|
lang_id, ok := c.Locals("langID").(uint)
|
||||||
|
if !ok {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
||||||
|
}
|
||||||
|
menu, err := h.menuService.GetRoutes(lang_id)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(err)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(response.Make(&menu, 0, i18n.T_(c, response.Message_OK)))
|
||||||
|
}
|
||||||
@@ -24,7 +24,6 @@ func MenuHandlerRoutes(r fiber.Router) fiber.Router {
|
|||||||
handler := NewMenuHandler()
|
handler := NewMenuHandler()
|
||||||
|
|
||||||
r.Get("/get-menu", handler.GetMenu)
|
r.Get("/get-menu", handler.GetMenu)
|
||||||
r.Get("/get-routes", handler.GetRouting)
|
|
||||||
r.Get("/get-top-menu", handler.GetTopMenu)
|
r.Get("/get-top-menu", handler.GetTopMenu)
|
||||||
|
|
||||||
return r
|
return r
|
||||||
@@ -45,21 +44,6 @@ func (h *MenuHandler) GetMenu(c fiber.Ctx) error {
|
|||||||
return c.JSON(response.Make(&menu, 0, i18n.T_(c, response.Message_OK)))
|
return c.JSON(response.Make(&menu, 0, i18n.T_(c, response.Message_OK)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *MenuHandler) GetRouting(c fiber.Ctx) error {
|
|
||||||
lang_id, ok := c.Locals("langID").(uint)
|
|
||||||
if !ok {
|
|
||||||
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
|
||||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
|
||||||
}
|
|
||||||
menu, err := h.menuService.GetRoutes(lang_id)
|
|
||||||
if err != nil {
|
|
||||||
return c.Status(responseErrors.GetErrorStatus(err)).
|
|
||||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.JSON(response.Make(&menu, 0, i18n.T_(c, response.Message_OK)))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *MenuHandler) GetTopMenu(c fiber.Ctx) error {
|
func (h *MenuHandler) GetTopMenu(c fiber.Ctx) error {
|
||||||
lang_id, ok := c.Locals("langID").(uint)
|
lang_id, ok := c.Locals("langID").(uint)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|||||||
@@ -89,6 +89,9 @@ func (s *Server) Setup() error {
|
|||||||
auth := s.public.Group("/auth")
|
auth := s.public.Group("/auth")
|
||||||
public.AuthHandlerRoutes(auth)
|
public.AuthHandlerRoutes(auth)
|
||||||
|
|
||||||
|
menuRouting := s.public.Group("/menu")
|
||||||
|
public.RoutingHandlerRoutes(menuRouting)
|
||||||
|
|
||||||
// product translation routes (restricted)
|
// product translation routes (restricted)
|
||||||
productTranslation := s.restricted.Group("/product-translation")
|
productTranslation := s.restricted.Group("/product-translation")
|
||||||
restricted.ProductTranslationHandlerRoutes(productTranslation)
|
restricted.ProductTranslationHandlerRoutes(productTranslation)
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
|
import "encoding/json"
|
||||||
|
|
||||||
// ProductDescription contains all the information visible on webpage, in given language.
|
// ProductDescription contains all the information visible on webpage, in given language.
|
||||||
type ProductDescription struct {
|
type ProductDescription struct {
|
||||||
ProductID uint `gorm:"column:id_product;primaryKey" json:"product_id" form:"product_id"`
|
ProductID uint `gorm:"column:id_product;primaryKey" json:"product_id" form:"product_id"`
|
||||||
@@ -28,20 +30,30 @@ type ProductRow struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type MeiliSearchProduct struct {
|
type MeiliSearchProduct struct {
|
||||||
ProductID uint `gorm:"column:id_product"`
|
ProductID uint `gorm:"column:id_product" json:"product_id"`
|
||||||
Name string `gorm:"column:name"`
|
Name string `gorm:"column:name" json:"name"`
|
||||||
Description string `gorm:"column:description"`
|
Active uint8 `gorm:"column:active" json:"active"`
|
||||||
DescriptionShort string `gorm:"column:description_short"`
|
Description string `gorm:"column:description" json:"description"`
|
||||||
Usage string `gorm:"column:used_for"`
|
DescriptionShort string `gorm:"column:description_short" json:"description_short"`
|
||||||
EAN13 string `gorm:"column:ean13"`
|
Usage string `gorm:"column:used_for" json:"usage"`
|
||||||
Reference string `gorm:"column:reference"`
|
EAN13 string `gorm:"column:ean13" json:"ean13"`
|
||||||
Width float64 `gorm:"column:width"`
|
Reference string `gorm:"column:reference" json:"reference"`
|
||||||
Height float64 `gorm:"column:height"`
|
Price float64 `gorm:"column:price" json:"price"`
|
||||||
Depth float64 `gorm:"column:depth"`
|
CategoryID uint `gorm:"column:id_category" json:"category_id"`
|
||||||
Weight float64 `gorm:"column:weight"`
|
CategoryName string `gorm:"column:category_name" json:"category_name"`
|
||||||
CategoryID uint `gorm:"column:id_category"`
|
Variations uint `gorm:"column:variations" json:"variations"`
|
||||||
CategoryName string `gorm:"column:category_name"`
|
|
||||||
Variations uint `gorm:"column:variations"`
|
// JSON fields stored as raw, converted to string for search
|
||||||
CategoryIDs []uint `gorm:"-"` // All category IDs including children for filtering
|
Attributes json.RawMessage `gorm:"column:attributes" json:"attributes"`
|
||||||
CoverImage string `gorm:"-"` // Cover image URL (not indexed)
|
Features json.RawMessage `gorm:"column:features" json:"features"`
|
||||||
|
AttributeFilters json.RawMessage `gorm:"column:attribute_filters" json:"attribute_filters"`
|
||||||
|
|
||||||
|
// String versions for searchable text (populated during indexing)
|
||||||
|
FeaturesStr string `json:"features_str"`
|
||||||
|
AttributesStr string `json:"attributes_str"`
|
||||||
|
AttributeFiltersStr string `json:"attribute_filters_str"`
|
||||||
|
|
||||||
|
CategoryIDs json.RawMessage `gorm:"column:category_ids" json:"category_ids"` // All category IDs including children for filtering
|
||||||
|
IDImage uint `gorm:"column:id_image" json:"id_image"` // Cover image ID (not indexed)
|
||||||
|
CoverImage string `json:"cover_image"` // Cover image URL (populated during indexing)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,13 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
type Route struct {
|
type Route struct {
|
||||||
ID uint `gorm:"primaryKey;autoIncrement"`
|
ID uint `gorm:"primaryKey;autoIncrement" json:"id"`
|
||||||
Name string `gorm:"type:varchar(255);not null;unique"`
|
Name string `gorm:"type:varchar(255);not null;unique" json:"name"`
|
||||||
Path *string `gorm:"type:varchar(255);default:null"`
|
Path *string `gorm:"type:varchar(255);default:null" json:"path,omitempty"`
|
||||||
Component string `gorm:"type:varchar(255);not null;comment:path to component file"`
|
Component string `gorm:"type:varchar(255);not null;comment:path to component file" json:"component"`
|
||||||
Layout *string `gorm:"type:varchar(50);default:'default';comment:'default | empty'"`
|
Meta *string `gorm:"type:longtext;default:'{}'" json:"meta,omitempty"`
|
||||||
Meta *string `gorm:"type:longtext;default:'{}'"`
|
Active *bool `gorm:"type:tinyint;default:1" json:"active,omitempty"`
|
||||||
IsActive *bool `gorm:"type:tinyint;default:1"`
|
SortOrder *int `gorm:"type:int;default:0" json:"sort_order,omitempty"`
|
||||||
SortOrder *int `gorm:"type:int;default:0"`
|
|
||||||
|
|
||||||
ParentID *uint `gorm:"index"`
|
|
||||||
Parent *Route `gorm:"constraint:OnUpdate:RESTRICT,OnDelete:SET NULL;foreignKey:ParentID"`
|
|
||||||
|
|
||||||
Children []Route `gorm:"foreignKey:ParentID"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (Route) TableName() string {
|
func (Route) TableName() string {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ type B2BTopMenu struct {
|
|||||||
MenuID int `gorm:"column:menu_id;primaryKey;autoIncrement" json:"menu_id"`
|
MenuID int `gorm:"column:menu_id;primaryKey;autoIncrement" json:"menu_id"`
|
||||||
Label json.RawMessage `gorm:"column:label;type:longtext;not null;default:'{}'" json:"label"`
|
Label json.RawMessage `gorm:"column:label;type:longtext;not null;default:'{}'" json:"label"`
|
||||||
ParentID *int `gorm:"column:parent_id;index:FK_b2b_top_menu_parent_id" json:"parent_id,omitempty"`
|
ParentID *int `gorm:"column:parent_id;index:FK_b2b_top_menu_parent_id" json:"parent_id,omitempty"`
|
||||||
Params string `gorm:"column:params;type:longtext;not null;default:'{}'" json:"params"`
|
Params json.RawMessage `gorm:"column:params;type:longtext;not null;default:'{}'" json:"params"`
|
||||||
Active int8 `gorm:"column:active;type:tinyint;not null;default:1" json:"active"`
|
Active int8 `gorm:"column:active;type:tinyint;not null;default:1" json:"active"`
|
||||||
Position int `gorm:"column:position;not null;default:1" json:"position"`
|
Position int `gorm:"column:position;not null;default:1" json:"position"`
|
||||||
|
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type UIProductDescriptionRepo interface {
|
type UIProductDescriptionRepo interface {
|
||||||
GetProductDescription(productID uint, productLangID uint) (*model.ProductDescription, error)
|
GetProductDescription(productID uint, productid_lang uint) (*model.ProductDescription, error)
|
||||||
CreateIfDoesNotExist(productID uint, productLangID uint) error
|
CreateIfDoesNotExist(productID uint, productid_lang uint) error
|
||||||
UpdateFields(productID uint, productLangID uint, updates map[string]string) error
|
UpdateFields(productID uint, productid_lang uint, updates map[string]string) error
|
||||||
GetMeiliProducts(id_lang uint) ([]model.MeiliSearchProduct, error)
|
GetMeiliProducts(id_lang uint) ([]model.MeiliSearchProduct, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,12 +22,12 @@ func New() UIProductDescriptionRepo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// We assume that any user has access to all product descriptions
|
// We assume that any user has access to all product descriptions
|
||||||
func (r *ProductDescriptionRepo) GetProductDescription(productID uint, productLangID uint) (*model.ProductDescription, error) {
|
func (r *ProductDescriptionRepo) GetProductDescription(productID uint, productid_lang uint) (*model.ProductDescription, error) {
|
||||||
var ProductDescription model.ProductDescription
|
var ProductDescription model.ProductDescription
|
||||||
|
|
||||||
err := db.DB.
|
err := db.DB.
|
||||||
Table("ps_product_lang").
|
Table("ps_product_lang").
|
||||||
Where("id_product = ? AND id_shop = ? AND id_lang = ?", productID, constdata.SHOP_ID, productLangID).
|
Where("id_product = ? AND id_shop = ? AND id_lang = ?", productID, constdata.SHOP_ID, productid_lang).
|
||||||
First(&ProductDescription).Error
|
First(&ProductDescription).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("database error: %w", err)
|
return nil, fmt.Errorf("database error: %w", err)
|
||||||
@@ -37,16 +37,16 @@ func (r *ProductDescriptionRepo) GetProductDescription(productID uint, productLa
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If it doesn't exist, returns an error.
|
// If it doesn't exist, returns an error.
|
||||||
func (r *ProductDescriptionRepo) CreateIfDoesNotExist(productID uint, productLangID uint) error {
|
func (r *ProductDescriptionRepo) CreateIfDoesNotExist(productID uint, productid_lang uint) error {
|
||||||
record := model.ProductDescription{
|
record := model.ProductDescription{
|
||||||
ProductID: productID,
|
ProductID: productID,
|
||||||
ShopID: constdata.SHOP_ID,
|
ShopID: constdata.SHOP_ID,
|
||||||
LangID: productLangID,
|
LangID: productid_lang,
|
||||||
}
|
}
|
||||||
|
|
||||||
err := db.DB.
|
err := db.DB.
|
||||||
Table("ps_product_lang").
|
Table("ps_product_lang").
|
||||||
Where("id_product = ? AND id_shop = ? AND id_lang = ?", productID, constdata.SHOP_ID, productLangID).
|
Where("id_product = ? AND id_shop = ? AND id_lang = ?", productID, constdata.SHOP_ID, productid_lang).
|
||||||
FirstOrCreate(&record).Error
|
FirstOrCreate(&record).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("database error: %w", err)
|
return fmt.Errorf("database error: %w", err)
|
||||||
@@ -55,7 +55,7 @@ func (r *ProductDescriptionRepo) CreateIfDoesNotExist(productID uint, productLan
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ProductDescriptionRepo) UpdateFields(productID uint, productLangID uint, updates map[string]string) error {
|
func (r *ProductDescriptionRepo) UpdateFields(productID uint, productid_lang uint, updates map[string]string) error {
|
||||||
if len(updates) == 0 {
|
if len(updates) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -66,7 +66,7 @@ func (r *ProductDescriptionRepo) UpdateFields(productID uint, productLangID uint
|
|||||||
|
|
||||||
err := db.DB.
|
err := db.DB.
|
||||||
Table("ps_product_lang").
|
Table("ps_product_lang").
|
||||||
Where("id_product = ? AND id_shop = ? AND id_lang = ?", productID, constdata.SHOP_ID, productLangID).
|
Where("id_product = ? AND id_shop = ? AND id_lang = ?", productID, constdata.SHOP_ID, productid_lang).
|
||||||
Updates(updatesIface).Error
|
Updates(updatesIface).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("database error: %w", err)
|
return fmt.Errorf("database error: %w", err)
|
||||||
@@ -79,96 +79,123 @@ func (r *ProductDescriptionRepo) UpdateFields(productID uint, productLangID uint
|
|||||||
func (r *ProductDescriptionRepo) GetMeiliProducts(id_lang uint) ([]model.MeiliSearchProduct, error) {
|
func (r *ProductDescriptionRepo) GetMeiliProducts(id_lang uint) ([]model.MeiliSearchProduct, error) {
|
||||||
var products []model.MeiliSearchProduct
|
var products []model.MeiliSearchProduct
|
||||||
|
|
||||||
err := db.DB.Debug().
|
query := db.Get().Debug().Raw(`
|
||||||
// Select(`
|
WITH products_page AS (
|
||||||
// ps.id_product AS id_product,
|
SELECT ps.id_product, ps.price
|
||||||
// pl.name AS name,
|
FROM ps_product_shop ps
|
||||||
// ps.price AS price,
|
WHERE ps.id_shop = ? AND ps.active = 1
|
||||||
// pl.description AS description,
|
),
|
||||||
// pl.description_short AS description_short,
|
variation_attributes AS (
|
||||||
// pl.usage AS used_for,
|
SELECT pas.id_product, pagl.public_name AS attribute_name,
|
||||||
// p.ean13 AS ean13,
|
JSON_ARRAYAGG(DISTINCT pal.name) AS attribute_values
|
||||||
// p.reference AS reference,
|
FROM ps_product_attribute_shop pas
|
||||||
// p.width AS width,
|
JOIN ps_product_attribute_combination ppac
|
||||||
// p.height AS height,
|
ON ppac.id_product_attribute = pas.id_product_attribute
|
||||||
// p.depth AS depth,
|
JOIN ps_attribute_lang pal
|
||||||
// p.weight AS weight,
|
ON pal.id_attribute = ppac.id_attribute AND pal.id_lang = ?
|
||||||
// ps.id_category_default AS id_category,
|
JOIN ps_attribute pa
|
||||||
// cl.name AS category_name,
|
ON pa.id_attribute = ppac.id_attribute
|
||||||
// COUNT(DISTINCT pas.id_product_attribute) AS variations
|
JOIN ps_attribute_group pag
|
||||||
// `).
|
ON pag.id_attribute_group = pa.id_attribute_group
|
||||||
// Table("ps_product_shop AS ps").
|
JOIN ps_attribute_group_lang pagl
|
||||||
// Joins("LEFT JOIN ps_product_lang AS pl ON ps.id_product = pl.id_product AND pl.id_shop = ? AND pl.id_lang = ?", constdata.SHOP_ID, id_lang).
|
ON pagl.id_attribute_group = pag.id_attribute_group AND pagl.id_lang = ?
|
||||||
// Joins("LEFT JOIN ps_product AS p ON p.id_product = ps.id_product").
|
WHERE pas.id_shop = ?
|
||||||
// Joins("LEFT JOIN ps_category_lang AS cl ON cl.id_category = ps.id_category_default AND cl.id_shop = ? AND cl.id_lang = ?", constdata.SHOP_ID, id_lang).
|
GROUP BY pas.id_product, pagl.public_name
|
||||||
// Joins("LEFT JOIN ps_product_attribute_shop AS pas ON pas.id_product = ps.id_product AND pas.id_shop = ?", constdata.SHOP_ID).
|
),
|
||||||
// Where("ps.id_shop = ? AND ps.active = 1", constdata.SHOP_ID).
|
variations AS (
|
||||||
// Group("ps.id_product").
|
SELECT id_product, JSON_OBJECTAGG(attribute_name, attribute_values) AS attributes
|
||||||
|
FROM variation_attributes
|
||||||
Select(`
|
GROUP BY id_product
|
||||||
ps.id_product AS id_product,
|
),
|
||||||
pl.name AS name,
|
variation_attribute_filters AS (
|
||||||
pl.description AS description,
|
SELECT pas.id_product,
|
||||||
pl.description_short AS description_short,
|
JSON_ARRAYAGG(
|
||||||
pl.usage AS used_for,
|
DISTINCT CONCAT(
|
||||||
p.ean13 AS ean13,
|
LOWER(REPLACE(CAST(pagl.public_name AS CHAR) COLLATE utf8mb4_unicode_ci, ' ', '_')),
|
||||||
p.reference AS reference,
|
':',
|
||||||
|
LOWER(REPLACE(CAST(pal.name AS CHAR) COLLATE utf8mb4_unicode_ci, ' ', '_'))
|
||||||
|
)
|
||||||
|
) AS attribute_filters
|
||||||
|
FROM ps_product_attribute_shop pas
|
||||||
|
JOIN ps_product_attribute_combination ppac
|
||||||
|
ON ppac.id_product_attribute = pas.id_product_attribute
|
||||||
|
JOIN ps_attribute_lang pal
|
||||||
|
ON pal.id_attribute = ppac.id_attribute AND pal.id_lang = ?
|
||||||
|
JOIN ps_attribute pa
|
||||||
|
ON pa.id_attribute = ppac.id_attribute
|
||||||
|
JOIN ps_attribute_group pag
|
||||||
|
ON pag.id_attribute_group = pa.id_attribute_group
|
||||||
|
JOIN ps_attribute_group_lang pagl
|
||||||
|
ON pagl.id_attribute_group = pag.id_attribute_group AND pagl.id_lang = ?
|
||||||
|
WHERE pas.id_shop = ?
|
||||||
|
GROUP BY pas.id_product
|
||||||
|
),
|
||||||
|
features AS (
|
||||||
|
SELECT pfp.id_product, JSON_OBJECTAGG(pfl.name, pfvl.value) AS features
|
||||||
|
FROM ps_feature_product pfp
|
||||||
|
JOIN ps_feature_lang pfl
|
||||||
|
ON pfl.id_feature = pfp.id_feature AND pfl.id_lang = ?
|
||||||
|
JOIN ps_feature_value_lang pfvl
|
||||||
|
ON pfvl.id_feature_value = pfp.id_feature_value AND pfvl.id_lang = ?
|
||||||
|
GROUP BY pfp.id_product
|
||||||
|
),
|
||||||
|
images AS (
|
||||||
|
SELECT id_product, id_image
|
||||||
|
FROM ps_image_shop
|
||||||
|
WHERE id_shop = ? AND cover = 1
|
||||||
|
),
|
||||||
|
categories AS (
|
||||||
|
SELECT id_product, JSON_ARRAYAGG(id_category) AS category_ids
|
||||||
|
FROM ps_category_product
|
||||||
|
GROUP BY id_product
|
||||||
|
)
|
||||||
|
SELECT pp.id_product,
|
||||||
|
pl.name,
|
||||||
|
TRIM(REGEXP_REPLACE(REGEXP_REPLACE(pl.description, '<[^>]*>', ' '), '[[:space:]]+', ' ')) AS description,
|
||||||
|
TRIM(REGEXP_REPLACE(REGEXP_REPLACE(pl.description_short, '<[^>]*>', ' '), '[[:space:]]+', ' ')) AS description_short,
|
||||||
|
TRIM(REGEXP_REPLACE(REGEXP_REPLACE(pl.usage, '<[^>]*>', ' '), '[[:space:]]+', ' ')) AS used_for,
|
||||||
|
p.ean13,
|
||||||
|
p.reference,
|
||||||
|
pp.price,
|
||||||
ps.id_category_default AS id_category,
|
ps.id_category_default AS id_category,
|
||||||
cl.name AS category_name,
|
cl.name AS category_name,
|
||||||
cl.link_rewrite as link_rewrite,
|
cl.link_rewrite,
|
||||||
COUNT(DISTINCT pas.id_product_attribute) AS variations,
|
COALESCE(vary.attributes, JSON_OBJECT()) AS attributes,
|
||||||
pis.id_image AS id_image,
|
COALESCE(vaf.attribute_filters, JSON_ARRAY()) AS attribute_filters,
|
||||||
GROUP_CONCAT(DISTINCT pcp.id_category) AS category_ids
|
COALESCE(feat.features, JSON_OBJECT()) AS features,
|
||||||
`).
|
img.id_image,
|
||||||
Table("ps_product_shop AS ps").
|
cat.category_ids,
|
||||||
Joins("LEFT JOIN ps_product_lang AS pl ON ps.id_product = pl.id_product AND pl.id_shop = ? AND pl.id_lang = ?", constdata.SHOP_ID, id_lang).
|
(SELECT COUNT(*) FROM ps_product_attribute_shop pas2 WHERE pas2.id_product = pp.id_product AND pas2.id_shop = ?) AS variations
|
||||||
Joins("LEFT JOIN ps_product AS p ON p.id_product = ps.id_product").
|
FROM products_page pp
|
||||||
Joins("LEFT JOIN ps_category_lang AS cl ON cl.id_category = ps.id_category_default AND cl.id_shop = ? AND cl.id_lang = ?", constdata.SHOP_ID, id_lang).
|
JOIN ps_product_shop ps ON ps.id_product = pp.id_product
|
||||||
Joins("LEFT JOIN ps_product_attribute_shop AS pas ON pas.id_product = ps.id_product AND pas.id_shop = ?", constdata.SHOP_ID).
|
JOIN ps_product_lang pl
|
||||||
Joins("JOIN ps_image_shop AS pis ON pis.id_product = ps.id_product AND pis.cover = 1").
|
ON pl.id_product = ps.id_product AND pl.id_shop = ? AND pl.id_lang = ?
|
||||||
Joins("JOIN ps_category_product AS pcp ON pcp.id_product = ps.id_product").
|
JOIN ps_product p ON p.id_product = ps.id_product
|
||||||
Where("ps.id_shop = ? AND ps.active = 1", constdata.SHOP_ID).
|
JOIN ps_category_lang cl
|
||||||
Group("pcp.id_product, ps.id_product").
|
ON cl.id_category = ps.id_category_default AND cl.id_shop = ? AND cl.id_lang = ?
|
||||||
Scan(&products).Error
|
LEFT JOIN variations vary ON vary.id_product = ps.id_product
|
||||||
if err != nil {
|
LEFT JOIN variation_attribute_filters vaf ON vaf.id_product = ps.id_product
|
||||||
|
LEFT JOIN features feat ON feat.id_product = ps.id_product
|
||||||
|
LEFT JOIN images img ON img.id_product = ps.id_product
|
||||||
|
LEFT JOIN categories cat ON cat.id_product = ps.id_product
|
||||||
|
ORDER BY ps.id_product
|
||||||
|
`,
|
||||||
|
constdata.SHOP_ID, // products_page
|
||||||
|
id_lang, id_lang, // variation_attributes pal.id_lang, pagl.id_lang
|
||||||
|
constdata.SHOP_ID, // variation_attributes pas.id_shop
|
||||||
|
id_lang, id_lang, // variation_attribute_filters pal.id_lang, pagl.id_lang
|
||||||
|
constdata.SHOP_ID, // variation_attribute_filters pas.id_shop
|
||||||
|
id_lang, id_lang, // features pfl.id_lang, pfvl.id_lang
|
||||||
|
constdata.SHOP_ID, // images id_shop
|
||||||
|
constdata.SHOP_ID, // variation count subquery
|
||||||
|
constdata.SHOP_ID, // ps_product_lang pl.id_shop
|
||||||
|
id_lang, // ps_product_lang pl.id_lang
|
||||||
|
constdata.SHOP_ID, // ps_category_lang cl.id_shop
|
||||||
|
id_lang, // ps_category_lang cl.id_lang
|
||||||
|
)
|
||||||
|
if err := query.Scan(&products).Error; err != nil {
|
||||||
return products, fmt.Errorf("database error: %w", err)
|
return products, fmt.Errorf("database error: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get all category IDs for each product (including child categories)
|
|
||||||
for i := range products {
|
|
||||||
var categoryIDs []uint
|
|
||||||
// Find all parent categories and their children using nested set
|
|
||||||
err := db.DB.
|
|
||||||
Table("ps_category AS c").
|
|
||||||
Select("c.id_category").
|
|
||||||
Joins("JOIN ps_category_product AS cp ON cp.id_category = c.id_category").
|
|
||||||
Joins("JOIN ps_category AS parent ON c.nleft >= parent.nleft AND c.nright <= parent.nright AND parent.id_category = ?", products[i].CategoryID).
|
|
||||||
Where("cp.id_product = ?", products[i].ProductID).
|
|
||||||
Group("c.id_category").
|
|
||||||
Pluck("c.id_category", &categoryIDs).Error
|
|
||||||
if err != nil {
|
|
||||||
continue // Skip if error, use default category
|
|
||||||
}
|
|
||||||
if len(categoryIDs) > 0 {
|
|
||||||
products[i].CategoryIDs = categoryIDs
|
|
||||||
} else {
|
|
||||||
products[i].CategoryIDs = []uint{products[i].CategoryID}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get cover image for the product
|
|
||||||
var imageID int
|
|
||||||
err = db.DB.
|
|
||||||
Table("ps_image AS i").
|
|
||||||
Select("i.id_image").
|
|
||||||
Joins("LEFT JOIN ps_image_shop AS iss ON iss.id_image = i.id_image AND iss.id_shop = ?", constdata.SHOP_ID).
|
|
||||||
Where("i.id_product = ? AND (i.cover = 1 OR i.cover IS TRUE)", products[i].ProductID).
|
|
||||||
Order("i.position ASC").
|
|
||||||
Limit(1).
|
|
||||||
Pluck("i.id_image", &imageID).Error
|
|
||||||
if err == nil && imageID > 0 {
|
|
||||||
products[i].CoverImage = fmt.Sprintf("%d/%d.jpg", products[i].ProductID, imageID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return products, nil
|
return products, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package routesrepo
|
|||||||
import (
|
import (
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/db"
|
"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/model"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/utils/nullable"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UIRoutesRepo interface {
|
type UIRoutesRepo interface {
|
||||||
@@ -18,7 +19,7 @@ func New() UIRoutesRepo {
|
|||||||
|
|
||||||
func (p *RoutesRepo) GetRoutes(langId uint) ([]model.Route, error) {
|
func (p *RoutesRepo) GetRoutes(langId uint) ([]model.Route, error) {
|
||||||
routes := []model.Route{}
|
routes := []model.Route{}
|
||||||
err := db.DB.Find(&routes).Error
|
err := db.DB.Find(&routes, model.Route{Active: nullable.GetNil(true)}).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package meiliService
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -31,81 +30,145 @@ func New() *MeiliService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getIndexName(id_lang uint) string {
|
||||||
|
return fmt.Sprintf("shop_%d_lang_%d", constdata.SHOP_ID, id_lang)
|
||||||
|
}
|
||||||
|
|
||||||
// ==================================== FOR TESTING ONLY ====================================
|
// ==================================== FOR TESTING ONLY ====================================
|
||||||
func (s *MeiliService) CreateIndex(id_lang uint) error {
|
func (s *MeiliService) CreateIndex(id_lang uint) error {
|
||||||
indexName := "meili_products_shop" + strconv.FormatInt(constdata.SHOP_ID, 10) + "_lang" + strconv.FormatInt(int64(id_lang), 10)
|
indexName := getIndexName(id_lang)
|
||||||
|
|
||||||
products, err := s.productDescriptionRepo.GetMeiliProducts(id_lang)
|
products, err := s.productDescriptionRepo.GetMeiliProducts(id_lang)
|
||||||
for i := 0; i < len(products); i++ {
|
if err != nil {
|
||||||
products[i].Description = cleanHTML(products[i].Description)
|
return fmt.Errorf("failed to get products: %w", err)
|
||||||
products[i].DescriptionShort = cleanHTML(products[i].DescriptionShort)
|
|
||||||
products[i].Usage = cleanHTML(products[i].Usage)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
primaryKey := "ProductID"
|
if len(products) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process products: prepare for indexing
|
||||||
|
for i := range products {
|
||||||
|
// Convert JSON fields to searchable strings
|
||||||
|
if len(products[i].Features) > 0 {
|
||||||
|
products[i].FeaturesStr = string(products[i].Features)
|
||||||
|
}
|
||||||
|
if len(products[i].Attributes) > 0 {
|
||||||
|
products[i].AttributesStr = string(products[i].Attributes)
|
||||||
|
}
|
||||||
|
if len(products[i].AttributeFilters) > 0 {
|
||||||
|
products[i].AttributeFiltersStr = string(products[i].AttributeFilters)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build cover image URL from image ID
|
||||||
|
if products[i].IDImage > 0 {
|
||||||
|
products[i].CoverImage = config.Get().Image.ImagePrefix + fmt.Sprintf("/%d", products[i].IDImage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add documents to index
|
||||||
|
primaryKey := "product_id"
|
||||||
docOptions := &meilisearch.DocumentOptions{
|
docOptions := &meilisearch.DocumentOptions{
|
||||||
PrimaryKey: &primaryKey,
|
PrimaryKey: &primaryKey,
|
||||||
SkipCreation: false,
|
SkipCreation: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
task, err := s.meiliClient.Index(indexName).AddDocuments(products, docOptions)
|
task, err := s.meiliClient.Index(indexName).AddDocuments(products, docOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("meili AddDocuments error: %w", err)
|
return fmt.Errorf("failed to add documents: %w", err)
|
||||||
}
|
}
|
||||||
finishedTask, err := s.meiliClient.WaitForTask(task.TaskUID, 500*time.Millisecond)
|
|
||||||
fmt.Printf("Task status: %s\n", finishedTask.Status)
|
|
||||||
fmt.Printf("Task error: %s\n", finishedTask.Error)
|
|
||||||
|
|
||||||
|
finishedTask, err := s.meiliClient.WaitForTask(task.TaskUID, 1000*time.Millisecond)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to wait for task: %w", err)
|
||||||
|
}
|
||||||
|
if finishedTask.Status == "failed" {
|
||||||
|
return fmt.Errorf("task failed: %v", finishedTask.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure filterable attributes
|
||||||
filterableAttributes := []interface{}{
|
filterableAttributes := []interface{}{
|
||||||
"CategoryID",
|
"product_id",
|
||||||
"CategoryIDs",
|
"category_id",
|
||||||
|
"category_ids",
|
||||||
|
"active",
|
||||||
|
"attribute_filters",
|
||||||
|
"features",
|
||||||
}
|
}
|
||||||
task, err = s.meiliClient.Index(indexName).UpdateFilterableAttributes(&filterableAttributes)
|
task, err = s.meiliClient.Index(indexName).UpdateFilterableAttributes(&filterableAttributes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("meili AddDocuments error: %w", err)
|
return fmt.Errorf("failed to update filterable attributes: %w", err)
|
||||||
|
}
|
||||||
|
_, err = s.meiliClient.WaitForTask(task.TaskUID, 500*time.Millisecond)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to wait for filterable task: %w", err)
|
||||||
}
|
}
|
||||||
finishedTask, err = s.meiliClient.WaitForTask(task.TaskUID, 500*time.Millisecond)
|
|
||||||
fmt.Printf("Task status: %s\n", finishedTask.Status)
|
|
||||||
fmt.Printf("Task error: %s\n", finishedTask.Error)
|
|
||||||
|
|
||||||
|
// Configure sortable attributes
|
||||||
|
sortableAttributes := []string{
|
||||||
|
"price",
|
||||||
|
"name",
|
||||||
|
"product_id",
|
||||||
|
"category_ids",
|
||||||
|
}
|
||||||
|
task, err = s.meiliClient.Index(indexName).UpdateSortableAttributes(&sortableAttributes)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to update sortable attributes: %w", err)
|
||||||
|
}
|
||||||
|
_, err = s.meiliClient.WaitForTask(task.TaskUID, 500*time.Millisecond)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to wait for sortable task: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure displayed attributes
|
||||||
displayedAttributes := []string{
|
displayedAttributes := []string{
|
||||||
"ProductID",
|
"product_id",
|
||||||
"Name",
|
"name",
|
||||||
"EAN13",
|
"ean13",
|
||||||
"Reference",
|
"reference",
|
||||||
"Variations",
|
"variations",
|
||||||
"CoverImage",
|
"id_image",
|
||||||
|
"price",
|
||||||
|
"category_name",
|
||||||
|
"category_ids",
|
||||||
|
"attribute_filters",
|
||||||
}
|
}
|
||||||
task, err = s.meiliClient.Index(indexName).UpdateDisplayedAttributes(&displayedAttributes)
|
task, err = s.meiliClient.Index(indexName).UpdateDisplayedAttributes(&displayedAttributes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("meili AddDocuments error: %w", err)
|
return fmt.Errorf("failed to update displayed attributes: %w", err)
|
||||||
|
}
|
||||||
|
_, err = s.meiliClient.WaitForTask(task.TaskUID, 500*time.Millisecond)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to wait for displayed task: %w", err)
|
||||||
}
|
}
|
||||||
finishedTask, err = s.meiliClient.WaitForTask(task.TaskUID, 500*time.Millisecond)
|
|
||||||
fmt.Printf("Task status: %s\n", finishedTask.Status)
|
|
||||||
fmt.Printf("Task error: %s\n", finishedTask.Error)
|
|
||||||
|
|
||||||
|
// Configure searchable attributes
|
||||||
searchableAttributes := []string{
|
searchableAttributes := []string{
|
||||||
"Name",
|
"name",
|
||||||
"DescriptionShort",
|
"description",
|
||||||
"Reference",
|
"description_short",
|
||||||
"EAN13",
|
"usage",
|
||||||
"CategoryName",
|
"features_str",
|
||||||
"Description",
|
"attributes_str",
|
||||||
"Usage",
|
"reference",
|
||||||
|
"ean13",
|
||||||
|
"category_name",
|
||||||
}
|
}
|
||||||
task, err = s.meiliClient.Index(indexName).UpdateSearchableAttributes(&searchableAttributes)
|
task, err = s.meiliClient.Index(indexName).UpdateSearchableAttributes(&searchableAttributes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("meili AddDocuments error: %w", err)
|
return fmt.Errorf("failed to update searchable attributes: %w", err)
|
||||||
|
}
|
||||||
|
_, err = s.meiliClient.WaitForTask(task.TaskUID, 500*time.Millisecond)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to wait for searchable task: %w", err)
|
||||||
}
|
}
|
||||||
finishedTask, err = s.meiliClient.WaitForTask(task.TaskUID, 500*time.Millisecond)
|
|
||||||
fmt.Printf("Task status: %s\n", finishedTask.Status)
|
|
||||||
fmt.Printf("Task error: %s\n", finishedTask.Error)
|
|
||||||
|
|
||||||
return err
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================================== FOR TESTING ONLY ====================================
|
// ==================================== FOR TESTING ONLY ====================================
|
||||||
func (s *MeiliService) Test(id_lang uint) (meilisearch.SearchResponse, error) {
|
func (s *MeiliService) Test(id_lang uint) (meilisearch.SearchResponse, error) {
|
||||||
indexName := "meili_products_shop" + strconv.FormatInt(constdata.SHOP_ID, 10) + "_lang" + strconv.FormatInt(int64(id_lang), 10)
|
indexName := getIndexName(id_lang)
|
||||||
|
|
||||||
searchReq := &meilisearch.SearchRequest{
|
searchReq := &meilisearch.SearchRequest{
|
||||||
Limit: 4,
|
Limit: 4,
|
||||||
@@ -128,7 +191,7 @@ func (s *MeiliService) Test(id_lang uint) (meilisearch.SearchResponse, error) {
|
|||||||
|
|
||||||
// Search performs a full-text search on the specified index
|
// Search performs a full-text search on the specified index
|
||||||
func (s *MeiliService) Search(id_lang uint, query string, id_category uint) (meilisearch.SearchResponse, error) {
|
func (s *MeiliService) Search(id_lang uint, query string, id_category uint) (meilisearch.SearchResponse, error) {
|
||||||
indexName := "meili_products_shop" + strconv.FormatInt(constdata.SHOP_ID, 10) + "_lang" + strconv.FormatInt(int64(id_lang), 10)
|
indexName := getIndexName(id_lang)
|
||||||
|
|
||||||
filter_query := "Active = 1"
|
filter_query := "Active = 1"
|
||||||
if id_category != 0 {
|
if id_category != 0 {
|
||||||
@@ -165,7 +228,7 @@ func (s *MeiliService) HealthCheck() (*meilisearch.Health, error) {
|
|||||||
|
|
||||||
// GetIndexSettings retrieves the current settings for a specific index
|
// GetIndexSettings retrieves the current settings for a specific index
|
||||||
func (s *MeiliService) GetIndexSettings(id_lang uint) (map[string]interface{}, error) {
|
func (s *MeiliService) GetIndexSettings(id_lang uint) (map[string]interface{}, error) {
|
||||||
indexName := "meili_products_shop" + strconv.FormatInt(constdata.SHOP_ID, 10) + "_lang" + strconv.FormatInt(int64(id_lang), 10)
|
indexName := getIndexName(id_lang)
|
||||||
|
|
||||||
index := s.meiliClient.Index(indexName)
|
index := s.meiliClient.Index(indexName)
|
||||||
|
|
||||||
|
|||||||
@@ -3,37 +3,35 @@ import { useFetchJson } from '@/composable/useFetchJson'
|
|||||||
import LangSwitch from './inner/langSwitch.vue'
|
import LangSwitch from './inner/langSwitch.vue'
|
||||||
import ThemeSwitch from './inner/themeSwitch.vue'
|
import ThemeSwitch from './inner/themeSwitch.vue'
|
||||||
import { useAuthStore } from '@/stores/auth'
|
import { useAuthStore } from '@/stores/auth'
|
||||||
import type { ApiResponse } from '@/types'
|
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import { currentLang } from '@/router/langs'
|
import { currentLang } from '@/router/langs'
|
||||||
|
import type { LabelTrans, TopMenuItem } from '@/types'
|
||||||
|
import type { NavigationMenuItem } from '@nuxt/ui'
|
||||||
|
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
let menu = ref()
|
let menu = ref()
|
||||||
async function getTopMenu() {
|
async function getTopMenu() {
|
||||||
try {
|
try {
|
||||||
const { items } = await useFetchJson<ApiResponse>('/api/v1/restricted/menu/get-top-menu')
|
const { items } = await useFetchJson<TopMenuItem>('/api/v1/restricted/menu/get-top-menu')
|
||||||
menu.value = items
|
menu.value = items
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err);
|
console.log(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const menuItems = computed(() =>
|
const menuItems = computed(() => transformMenu(menu.value[0].children, currentLang.value?.iso_code))
|
||||||
transformMenu(menu.value[0].children, currentLang.value?.iso_code)
|
function transformMenu(items: TopMenuItem[], locale: string | undefined): NavigationMenuItem[] {
|
||||||
)
|
|
||||||
function transformMenu(items, locale: string | undefined) {
|
|
||||||
return items.map((item) => {
|
return items.map((item) => {
|
||||||
const parsedLabel = JSON.parse(item.label)
|
let route = {
|
||||||
|
|
||||||
return {
|
|
||||||
icon: 'i-lucide-house',
|
icon: 'i-lucide-house',
|
||||||
label: parsedLabel.trans[locale] || parsedLabel.name,
|
label: item.label.trans[locale as keyof LabelTrans].label,
|
||||||
to: { name: parsedLabel.name },
|
children: item.children ? transformMenu(item.children, locale) : undefined,
|
||||||
children: item.children
|
|
||||||
? transformMenu(item.children, locale)
|
|
||||||
: undefined,
|
|
||||||
}
|
}
|
||||||
|
if (item.params?.route) {
|
||||||
|
route = { ...route, ...{ to: { name: item.params.route.name, params: { locale: locale } } } }
|
||||||
|
}
|
||||||
|
|
||||||
|
return route
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,8 +41,7 @@ await getTopMenu()
|
|||||||
<template>
|
<template>
|
||||||
{{ menuItems }}
|
{{ menuItems }}
|
||||||
<!-- fixed top-0 left-0 right-0 z-50 -->
|
<!-- fixed top-0 left-0 right-0 z-50 -->
|
||||||
<header
|
<header class="bg-white/80 dark:bg-(--black) backdrop-blur-md border-b border-(--border-light) dark:border-(--border-dark)">
|
||||||
class=" bg-white/80 dark:bg-(--black) backdrop-blur-md border-b border-(--border-light) dark:border-(--border-dark)">
|
|
||||||
<div class="container mx-auto px-4 sm:px-6 lg:px-8">
|
<div class="container mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
<div class="flex items-center justify-between h-14">
|
<div class="flex items-center justify-between h-14">
|
||||||
<!-- Logo -->
|
<!-- Logo -->
|
||||||
@@ -55,7 +52,7 @@ await getTopMenu()
|
|||||||
<span class="font-semibold text-gray-900 dark:text-white">TimeTracker</span>
|
<span class="font-semibold text-gray-900 dark:text-white">TimeTracker</span>
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
|
|
||||||
<UNavigationMenu :items="menuItems" />
|
<UNavigationMenu :items="menuItems" class="w-full" />
|
||||||
|
|
||||||
<!-- {{ router }} -->
|
<!-- {{ router }} -->
|
||||||
<!-- <RouterLink :to="{ name: 'admin-products' }">
|
<!-- <RouterLink :to="{ name: 'admin-products' }">
|
||||||
@@ -93,8 +90,11 @@ await getTopMenu()
|
|||||||
<!-- Theme Switcher -->
|
<!-- Theme Switcher -->
|
||||||
<ThemeSwitch />
|
<ThemeSwitch />
|
||||||
<!-- Logout Button (only when authenticated) -->
|
<!-- Logout Button (only when authenticated) -->
|
||||||
<button v-if="authStore.isAuthenticated" @click="authStore.logout()"
|
<button
|
||||||
class="px-3 py-1.5 text-sm font-medium text-black dark:text-white hover:text-black dark:hover:text-white hover:bg-gray-100 dark:hover:bg-gray-600 rounded-lg transition-colors border border-(--border-light) dark:border-(--border-dark)">
|
v-if="authStore.isAuthenticated"
|
||||||
|
@click="authStore.logout()"
|
||||||
|
class="px-3 py-1.5 text-sm font-medium text-black dark:text-white hover:text-black dark:hover:text-white hover:bg-gray-100 dark:hover:bg-gray-600 rounded-lg transition-colors border border-(--border-light) dark:border-(--border-dark)"
|
||||||
|
>
|
||||||
{{ $t('general.logout') }}
|
{{ $t('general.logout') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,6 +10,18 @@ function isAuthenticated(): boolean {
|
|||||||
}
|
}
|
||||||
await getSettings()
|
await getSettings()
|
||||||
|
|
||||||
|
const routes = await getRoutes()
|
||||||
|
let newRoutes = []
|
||||||
|
for (let r of routes) {
|
||||||
|
const component = () => import(/* @vite-ignore */ `..${r.component}`)
|
||||||
|
newRoutes.push({
|
||||||
|
path: r.path,
|
||||||
|
component,
|
||||||
|
name: r.name,
|
||||||
|
meta: r.meta ? JSON.parse(r.meta) : {},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory(import.meta.env.VITE_BASE_URL),
|
history: createWebHistory(import.meta.env.VITE_BASE_URL),
|
||||||
routes: [
|
routes: [
|
||||||
@@ -21,8 +33,9 @@ const router = createRouter({
|
|||||||
path: '/:locale',
|
path: '/:locale',
|
||||||
name: 'locale',
|
name: 'locale',
|
||||||
children: [
|
children: [
|
||||||
|
...newRoutes,
|
||||||
{
|
{
|
||||||
path: '/:pathMatch(.*)*',
|
path: ':pathMatch(.*)*',
|
||||||
component: () => import('@/views/NotFoundView.vue'),
|
component: () => import('@/views/NotFoundView.vue'),
|
||||||
name: 'not-found-child',
|
name: 'not-found-child',
|
||||||
},
|
},
|
||||||
@@ -36,25 +49,6 @@ const router = createRouter({
|
|||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
await getRoutes().then(routes => {
|
|
||||||
const modules = import.meta.glob('/src/**/**/*.vue')
|
|
||||||
routes.forEach(item => {
|
|
||||||
const component = modules[`/src${item.Component}`]
|
|
||||||
|
|
||||||
if (!component) {
|
|
||||||
console.error('Component not found:', item.Component)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
router.addRoute('locale', {
|
|
||||||
path: item.Path,
|
|
||||||
component,
|
|
||||||
name: item.Name,
|
|
||||||
meta: item.Meta ? JSON.parse(item.Meta) : {}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
router.beforeEach((to, from) => {
|
router.beforeEach((to, from) => {
|
||||||
const locale = to.params.locale as string
|
const locale = to.params.locale as string
|
||||||
const localeLang = langs.find((x) => x.iso_code === locale)
|
const localeLang = langs.find((x) => x.iso_code === locale)
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ export const getMenu = async () => {
|
|||||||
|
|
||||||
|
|
||||||
export const getRoutes = async () => {
|
export const getRoutes = async () => {
|
||||||
const resp = await useFetchJson<Route[]>('/api/v1/restricted/menu/get-routes');
|
const resp = await useFetchJson<Route[]>('/api/v1/public/menu/get-routes');
|
||||||
|
|
||||||
return resp.items
|
return resp.items
|
||||||
|
|
||||||
|
|||||||
5
bo/src/types/index.d.ts
vendored
5
bo/src/types/index.d.ts
vendored
@@ -1,8 +1,9 @@
|
|||||||
export * from '@types/lang'
|
export * from '@types/lang'
|
||||||
export * from '@types/response'
|
export * from '@types/response'
|
||||||
|
export * from '@types/menu'
|
||||||
|
|
||||||
export interface ApiResponse {
|
export interface ApiResponse<T> {
|
||||||
message: string
|
message: string
|
||||||
items: Product[]
|
items: T
|
||||||
count: number
|
count: number
|
||||||
}
|
}
|
||||||
|
|||||||
51
bo/src/types/menu.d.ts
vendored
51
bo/src/types/menu.d.ts
vendored
@@ -12,15 +12,42 @@ export interface Params {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface Route {
|
export interface Route {
|
||||||
ID: number
|
id: number
|
||||||
Name: string
|
name: string
|
||||||
Path: string
|
path: string
|
||||||
Component: string
|
component: string
|
||||||
Layout: string
|
meta: string
|
||||||
Meta: string
|
active: boolean
|
||||||
IsActive: boolean
|
}
|
||||||
SortOrder: number
|
|
||||||
ParentID: any
|
export interface TopMenuItem {
|
||||||
Parent: any
|
menu_id: number
|
||||||
Children: any
|
label: Label
|
||||||
}
|
params: TopMenuParams
|
||||||
|
active: number
|
||||||
|
position: number
|
||||||
|
children: TopMenuItem[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Label {
|
||||||
|
label: string
|
||||||
|
trans:LabelTrans
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LabelTrans{
|
||||||
|
pl:LabelItem
|
||||||
|
en:LabelItem
|
||||||
|
de: LabelItem
|
||||||
|
}
|
||||||
|
export interface LabelItem {
|
||||||
|
label: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TopMenuParams {
|
||||||
|
route: TopMenuRoute
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TopMenuRoute {
|
||||||
|
name: string
|
||||||
|
params: Record<string, string>
|
||||||
|
}
|
||||||
|
|||||||
9
bo/src/views/HomeView.vue
Normal file
9
bo/src/views/HomeView.vue
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<template>
|
||||||
|
<component :is="Default || 'div'">
|
||||||
|
home View
|
||||||
|
</component>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import Default from '@/layouts/default.vue';
|
||||||
|
|
||||||
|
</script>
|
||||||
@@ -5,28 +5,21 @@ CREATE TABLE IF NOT EXISTS b2b_routes (
|
|||||||
name VARCHAR(255) NOT NULL UNIQUE,
|
name VARCHAR(255) NOT NULL UNIQUE,
|
||||||
path VARCHAR(255) NULL,
|
path VARCHAR(255) NULL,
|
||||||
component VARCHAR(255) NOT NULL COMMENT 'path to component file',
|
component VARCHAR(255) NOT NULL COMMENT 'path to component file',
|
||||||
layout VARCHAR(50) DEFAULT 'default' COMMENT "'default' | 'empty'",
|
|
||||||
meta JSON DEFAULT '{}',
|
meta JSON DEFAULT '{}',
|
||||||
is_active BOOLEAN DEFAULT TRUE,
|
active BOOLEAN DEFAULT TRUE
|
||||||
sort_order INT DEFAULT 0,
|
|
||||||
parent_id INT NULL,
|
|
||||||
|
|
||||||
CONSTRAINT fk_parent
|
|
||||||
FOREIGN KEY (parent_id)
|
|
||||||
REFERENCES b2b_routes(id)
|
|
||||||
ON DELETE SET NULL
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
|
|
||||||
INSERT IGNORE INTO b2b_routes
|
INSERT IGNORE INTO b2b_routes
|
||||||
(name, path, component, layout, meta, is_active, sort_order, parent_id)
|
(name, path, component, meta, active)
|
||||||
VALUES
|
VALUES
|
||||||
('root', '', '', 'default', '{"trans": "route.root"}', 0, 0, 0),
|
('root', '', '', '{"trans": "route.root"}', 0),
|
||||||
('home', '', '@/views/HomeView.vue', 'default', '{"trans": "route.home"}', 1, 0, 0),
|
('home', '', '/views/HomeView.vue', '{"trans": "route.home"}', 1),
|
||||||
('login', 'login', '@/views/LoginView.vue', 'empty', '{"guest":true}', 1, 2, NULL),
|
('login', 'login', '/views/LoginView.vue', '{"guest":true}', 1),
|
||||||
('register', 'register', '@/views/RegisterView.vue', 'empty', '{"guest":true}', 1, 3, NULL),
|
('register', 'register', '/views/RegisterView.vue', '{"guest":true}', 1),
|
||||||
('password-recovery', 'password-recovery', '@/views/PasswordRecoveryView.vue', 'empty', '{"guest":true}', 1, 4, NULL),
|
('password-recovery', 'password-recovery', '/views/PasswordRecoveryView.vue', '{"guest":true}', 1),
|
||||||
('reset-password', 'reset-password', '@/views/ResetPasswordView.vue', 'empty', '{"guest":true}', 1, 5, NULL),
|
('reset-password', 'reset-password', '/views/ResetPasswordView.vue', '{"guest":true}', 1),
|
||||||
('verify-email', 'verify-email', '@/views/VerifyEmailView.vue', 'empty', '{"guest":true}', 1, 6, NULL);
|
('verify-email', 'verify-email', '/views/VerifyEmailView.vue', '{"guest":true}', 1);
|
||||||
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS b2b_top_menu (
|
CREATE TABLE IF NOT EXISTS b2b_top_menu (
|
||||||
@@ -44,10 +37,10 @@ CREATE TABLE IF NOT EXISTS b2b_top_menu (
|
|||||||
) ENGINE = InnoDB;
|
) ENGINE = InnoDB;
|
||||||
|
|
||||||
|
|
||||||
INSERT INTO `b2b_top_menu` (`menu_id`, `label`, `parent_id`, `params`, `active`, `position`) VALUES
|
INSERT IGNORE INTO `b2b_top_menu` (`menu_id`, `label`, `parent_id`, `params`, `active`, `position`) VALUES
|
||||||
(1, JSON_COMPACT('{"name":"root","trans":{"pl":{"label":"Menu główne"},"en":{"label":"Main Menu"},"de":{"label":"Hauptmenü"}}}'),NULL,'{}',1,1),
|
(1, JSON_COMPACT('{"name":"root","trans":{"pl":{"label":"Menu główne"},"en":{"label":"Main Menu"},"de":{"label":"Hauptmenü"}}}'),NULL,JSON_COMPACT('{}'),1,1),
|
||||||
(3, JSON_COMPACT('{"name":"admin-products","trans":{"pl":{"label":"admin-products"},"en":{"label":"admin-products"},"de":{"label":"admin-products"}}}'),1,'{}',1,1),
|
(3, JSON_COMPACT('{"name":"admin-products","trans":{"pl":{"label":"admin-products"},"en":{"label":"admin-products"},"de":{"label":"admin-products"}}}'),1,JSON_COMPACT('{}'),1,1),
|
||||||
(9, JSON_COMPACT('{"name":"carts","trans":{"pl":{"label":"Koszyki"},"en":{"label":"Carts"},"de":{"label":"Warenkörbe"}}}'),3,'{}',1,1);
|
(9, JSON_COMPACT('{"name":"carts","trans":{"pl":{"label":"Koszyki"},"en":{"label":"Carts"},"de":{"label":"Warenkörbe"}}}'),3,JSON_COMPACT('{"route": {"name": "home", "params":{"locale": ""}}}'),1,1);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -55,4 +48,4 @@ INSERT INTO `b2b_top_menu` (`menu_id`, `label`, `parent_id`, `params`, `active`,
|
|||||||
|
|
||||||
DROP TABLE IF EXISTS b2b_routes;
|
DROP TABLE IF EXISTS b2b_routes;
|
||||||
DROP TABLE IF EXISTS b2b_top_menu;
|
DROP TABLE IF EXISTS b2b_top_menu;
|
||||||
|
DROP FUNCTION IF EXISTS `slugify_eu`;
|
||||||
|
|||||||
@@ -138,6 +138,68 @@ VALUES
|
|||||||
(3, 'Čeština', 2, '🇨🇿'),
|
(3, 'Čeština', 2, '🇨🇿'),
|
||||||
(4, 'Deutschland', 2, '🇩🇪');
|
(4, 'Deutschland', 2, '🇩🇪');
|
||||||
|
|
||||||
|
|
||||||
|
DELIMITER //
|
||||||
|
|
||||||
|
CREATE FUNCTION IF NOT EXISTS slugify_eu(input TEXT)
|
||||||
|
RETURNS TEXT
|
||||||
|
DETERMINISTIC
|
||||||
|
BEGIN
|
||||||
|
DECLARE s TEXT;
|
||||||
|
|
||||||
|
SET s = LOWER(input);
|
||||||
|
|
||||||
|
-- spaces
|
||||||
|
SET s = REPLACE(s,' ','_');
|
||||||
|
|
||||||
|
-- Polish
|
||||||
|
SET s = REPLACE(s,'ą','a');
|
||||||
|
SET s = REPLACE(s,'ć','c');
|
||||||
|
SET s = REPLACE(s,'ę','e');
|
||||||
|
SET s = REPLACE(s,'ł','l');
|
||||||
|
SET s = REPLACE(s,'ń','n');
|
||||||
|
SET s = REPLACE(s,'ó','o');
|
||||||
|
SET s = REPLACE(s,'ś','s');
|
||||||
|
SET s = REPLACE(s,'ż','z');
|
||||||
|
SET s = REPLACE(s,'ź','z');
|
||||||
|
|
||||||
|
-- German
|
||||||
|
SET s = REPLACE(s,'ä','a');
|
||||||
|
SET s = REPLACE(s,'ö','o');
|
||||||
|
SET s = REPLACE(s,'ü','u');
|
||||||
|
SET s = REPLACE(s,'ß','ss');
|
||||||
|
|
||||||
|
-- French
|
||||||
|
SET s = REPLACE(s,'à','a');
|
||||||
|
SET s = REPLACE(s,'â','a');
|
||||||
|
SET s = REPLACE(s,'æ','ae');
|
||||||
|
SET s = REPLACE(s,'ç','c');
|
||||||
|
SET s = REPLACE(s,'è','e');
|
||||||
|
SET s = REPLACE(s,'é','e');
|
||||||
|
SET s = REPLACE(s,'ê','e');
|
||||||
|
SET s = REPLACE(s,'ë','e');
|
||||||
|
SET s = REPLACE(s,'î','i');
|
||||||
|
SET s = REPLACE(s,'ï','i');
|
||||||
|
SET s = REPLACE(s,'ô','o');
|
||||||
|
SET s = REPLACE(s,'ù','u');
|
||||||
|
SET s = REPLACE(s,'û','u');
|
||||||
|
SET s = REPLACE(s,'ü','u');
|
||||||
|
SET s = REPLACE(s,'ÿ','y');
|
||||||
|
|
||||||
|
-- Spanish / Portuguese
|
||||||
|
SET s = REPLACE(s,'á','a');
|
||||||
|
SET s = REPLACE(s,'í','i');
|
||||||
|
SET s = REPLACE(s,'ñ','n');
|
||||||
|
|
||||||
|
-- Scandinavian
|
||||||
|
SET s = REPLACE(s,'å','a');
|
||||||
|
SET s = REPLACE(s,'ø','o');
|
||||||
|
|
||||||
|
RETURN s;
|
||||||
|
END //
|
||||||
|
|
||||||
|
DELIMITER ;
|
||||||
|
|
||||||
-- +goose Down
|
-- +goose Down
|
||||||
|
|
||||||
DROP TABLE IF EXISTS b2b_countries;
|
DROP TABLE IF EXISTS b2b_countries;
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ INSERT IGNORE b2b_scopes (id, name) VALUES (3, 'backoffice');
|
|||||||
-- Translations
|
-- Translations
|
||||||
|
|
||||||
-- Component: general
|
-- Component: general
|
||||||
INSERT IGNORE b2b_translations (lang_id, scope_id, component_id, `key`, data) VALUES
|
INSERT b2b_translations (lang_id, scope_id, component_id, `key`, data) VALUES
|
||||||
(1, 3, 300, 'already_have_an_account', 'Masz już konto?'),
|
(1, 3, 300, 'already_have_an_account', 'Masz już konto?'),
|
||||||
(1, 3, 300, 'and', 'i'),
|
(1, 3, 300, 'and', 'i'),
|
||||||
(1, 3, 300, 'back_to_sign_in', 'Powrót do logowania'),
|
(1, 3, 300, 'back_to_sign_in', 'Powrót do logowania'),
|
||||||
@@ -123,10 +123,12 @@ INSERT IGNORE b2b_translations (lang_id, scope_id, component_id, `key`, data) VA
|
|||||||
(3, 3, 300, 'reset_password', 'Resetovat heslo'),
|
(3, 3, 300, 'reset_password', 'Resetovat heslo'),
|
||||||
(3, 3, 300, 'send_password_reset_link', 'Odeslat odkaz pro obnovení hesla'),
|
(3, 3, 300, 'send_password_reset_link', 'Odeslat odkaz pro obnovení hesla'),
|
||||||
(3, 3, 300, 'sign_in', 'Přihlásit se'),
|
(3, 3, 300, 'sign_in', 'Přihlásit se'),
|
||||||
(3, 3, 300, 'terms_of_service', 'Podmínky služby');
|
(3, 3, 300, 'terms_of_service', 'Podmínky služby')
|
||||||
|
ON DUPLICATE KEY UPDATE
|
||||||
|
data = IF(data IS NULL, VALUES(data), data);
|
||||||
|
|
||||||
-- Component: validate_error
|
-- Component: validate_error
|
||||||
INSERT IGNORE b2b_translations (lang_id, scope_id, component_id, `key`, data) VALUES
|
INSERT b2b_translations (lang_id, scope_id, component_id, `key`, data) VALUES
|
||||||
(1, 3, 301, 'confirm_password_required', 'Potwierdź hasło'),
|
(1, 3, 301, 'confirm_password_required', 'Potwierdź hasło'),
|
||||||
(1, 3, 301, 'email_required', 'Adres e-mail jest wymagany'),
|
(1, 3, 301, 'email_required', 'Adres e-mail jest wymagany'),
|
||||||
(1, 3, 301, 'first_name_required', 'Imię jest wymagane'),
|
(1, 3, 301, 'first_name_required', 'Imię jest wymagane'),
|
||||||
@@ -150,10 +152,12 @@ INSERT IGNORE b2b_translations (lang_id, scope_id, component_id, `key`, data) VA
|
|||||||
(3, 3, 301, 'no_issues_for_quarter', 'Nebyla nalezena žádná issues pro toto čtvrtletí'),
|
(3, 3, 301, 'no_issues_for_quarter', 'Nebyla nalezena žádná issues pro toto čtvrtletí'),
|
||||||
(3, 3, 301, 'password_required', 'Heslo je povinné'),
|
(3, 3, 301, 'password_required', 'Heslo je povinné'),
|
||||||
(3, 3, 301, 'registration_validation_password_not_same', 'Hesla se neshodují'),
|
(3, 3, 301, 'registration_validation_password_not_same', 'Hesla se neshodují'),
|
||||||
(3, 3, 301, 'registration_validation_password_requirements', 'Požadavky na heslo při registraci');
|
(3, 3, 301, 'registration_validation_password_requirements', 'Požadavky na heslo při registraci')
|
||||||
|
ON DUPLICATE KEY UPDATE
|
||||||
|
data = IF(data IS NULL, VALUES(data), data);
|
||||||
|
|
||||||
-- Component: repo_chart
|
-- Component: repo_chart
|
||||||
INSERT IGNORE b2b_translations (lang_id, scope_id, component_id, `key`, data) VALUES
|
INSERT b2b_translations (lang_id, scope_id, component_id, `key`, data) VALUES
|
||||||
(1, 3, 302, 'all_quarters', 'Wszystkie kwartały'),
|
(1, 3, 302, 'all_quarters', 'Wszystkie kwartały'),
|
||||||
(1, 3, 302, 'created_on', 'Utworzono'),
|
(1, 3, 302, 'created_on', 'Utworzono'),
|
||||||
(1, 3, 302, 'failed_to_load_issues', 'Nie udało się załadować zadań'),
|
(1, 3, 302, 'failed_to_load_issues', 'Nie udało się załadować zadań'),
|
||||||
@@ -231,7 +235,9 @@ INSERT IGNORE b2b_translations (lang_id, scope_id, component_id, `key`, data) VA
|
|||||||
(3, 3, 302, 'user_initials', 'Iniciály uživatele'),
|
(3, 3, 302, 'user_initials', 'Iniciály uživatele'),
|
||||||
(3, 3, 302, 'work_by_quarter', 'Práce dokončená po čtvrtletích (hodiny)'),
|
(3, 3, 302, 'work_by_quarter', 'Práce dokončená po čtvrtletích (hodiny)'),
|
||||||
(3, 3, 302, 'work_done_by_quarter', 'práce provedená za čtvrtletí'),
|
(3, 3, 302, 'work_done_by_quarter', 'práce provedená za čtvrtletí'),
|
||||||
(3, 3, 302, 'year', 'Rok');
|
(3, 3, 302, 'year', 'Rok')
|
||||||
|
ON DUPLICATE KEY UPDATE
|
||||||
|
data = IF(data IS NULL, VALUES(data), data);
|
||||||
|
|
||||||
-- Component: verify_email
|
-- Component: verify_email
|
||||||
INSERT IGNORE b2b_translations (lang_id, scope_id, component_id, `key`, data) VALUES
|
INSERT IGNORE b2b_translations (lang_id, scope_id, component_id, `key`, data) VALUES
|
||||||
@@ -267,7 +273,9 @@ INSERT IGNORE b2b_translations (lang_id, scope_id, component_id, `key`, data) VA
|
|||||||
(3, 3, 303, 'success_message', 'Váš e-mail byl úspěšně ověřen.'),
|
(3, 3, 303, 'success_message', 'Váš e-mail byl úspěšně ověřen.'),
|
||||||
(3, 3, 303, 'success_title', 'E-mail ověřen!'),
|
(3, 3, 303, 'success_title', 'E-mail ověřen!'),
|
||||||
(3, 3, 303, 'verification_failed', 'Ověření e-mailu selhalo'),
|
(3, 3, 303, 'verification_failed', 'Ověření e-mailu selhalo'),
|
||||||
(3, 3, 303, 'verifying', 'Ověřování vašeho e-mailu...');
|
(3, 3, 303, 'verifying', 'Ověřování vašeho e-mailu...')
|
||||||
|
ON DUPLICATE KEY UPDATE
|
||||||
|
data = IF(data IS NULL, VALUES(data), data);
|
||||||
|
|
||||||
-- +goose Down
|
-- +goose Down
|
||||||
-- Remove translations for this scope
|
-- Remove translations for this scope
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ INSERT IGNORE b2b_scopes (id, name) VALUES (1, 'Backend');
|
|||||||
-- (3, 1, 1, 'translations_not_loaded', 'Übersetzungen konnten nicht geladen werden');
|
-- (3, 1, 1, 'translations_not_loaded', 'Übersetzungen konnten nicht geladen werden');
|
||||||
|
|
||||||
-- Component: email (component_id = 100)
|
-- Component: email (component_id = 100)
|
||||||
INSERT IGNORE b2b_translations (lang_id, scope_id, component_id, `key`, data) VALUES
|
INSERT b2b_translations (lang_id, scope_id, component_id, `key`, data) VALUES
|
||||||
(1, 1, 100, 'langs_loaded', 'Języki załadowane'),
|
(1, 1, 100, 'langs_loaded', 'Języki załadowane'),
|
||||||
(1, 1, 100, 'langs_not_loaded', 'Nie udało się załadować języków'),
|
(1, 1, 100, 'langs_not_loaded', 'Nie udało się załadować języków'),
|
||||||
(1, 1, 100, 'message_nok', 'Błąd'),
|
(1, 1, 100, 'message_nok', 'Błąd'),
|
||||||
@@ -64,10 +64,12 @@ INSERT IGNORE b2b_translations (lang_id, scope_id, component_id, `key`, data) VA
|
|||||||
(3, 1, 100, 'message_nok', 'Fehler'),
|
(3, 1, 100, 'message_nok', 'Fehler'),
|
||||||
(3, 1, 100, 'message_ok', 'Erfolg'),
|
(3, 1, 100, 'message_ok', 'Erfolg'),
|
||||||
(3, 1, 100, 'translations_loaded', 'Übersetzungen geladen'),
|
(3, 1, 100, 'translations_loaded', 'Übersetzungen geladen'),
|
||||||
(3, 1, 100, 'translations_not_loaded', 'Übersetzungen konnten nicht geladen werden');
|
(3, 1, 100, 'translations_not_loaded', 'Übersetzungen konnten nicht geladen werden')
|
||||||
|
ON DUPLICATE KEY UPDATE
|
||||||
|
data = IF(data IS NULL, VALUES(data), data);
|
||||||
|
|
||||||
-- Component: error (component_id = 101)
|
-- Component: error (component_id = 101)
|
||||||
INSERT IGNORE b2b_translations (lang_id, scope_id, component_id, `key`, data) VALUES
|
INSERT b2b_translations (lang_id, scope_id, component_id, `key`, data) VALUES
|
||||||
(1, 1, 101, 'err_bad_paging', 'zła paginacja'),
|
(1, 1, 101, 'err_bad_paging', 'zła paginacja'),
|
||||||
(1, 1, 101, 'err_bad_quarter_attribute', 'nieprawidłowy atrybut quarter'),
|
(1, 1, 101, 'err_bad_quarter_attribute', 'nieprawidłowy atrybut quarter'),
|
||||||
(1, 1, 101, 'err_bad_repo_id_attribute', 'nieprawidłowy atrybut repoID'),
|
(1, 1, 101, 'err_bad_repo_id_attribute', 'nieprawidłowy atrybut repoID'),
|
||||||
@@ -150,7 +152,9 @@ INSERT IGNORE b2b_translations (lang_id, scope_id, component_id, `key`, data) VA
|
|||||||
(3, 1, 101, 'err_token_required', 'Token erforderlich'),
|
(3, 1, 101, 'err_token_required', 'Token erforderlich'),
|
||||||
(3, 1, 101, 'err_user_inactive', 'Benutzerkonto ist inaktiv'),
|
(3, 1, 101, 'err_user_inactive', 'Benutzerkonto ist inaktiv'),
|
||||||
(3, 1, 101, 'err_user_not_found', 'Benutzer nicht gefunden'),
|
(3, 1, 101, 'err_user_not_found', 'Benutzer nicht gefunden'),
|
||||||
(3, 1, 101, 'err_verification_token_expired', 'Verifizierungstoken abgelaufen');
|
(3, 1, 101, 'err_verification_token_expired', 'Verifizierungstoken abgelaufen')
|
||||||
|
ON DUPLICATE KEY UPDATE
|
||||||
|
data = IF(data IS NULL, VALUES(data), data);
|
||||||
|
|
||||||
-- +goose Down
|
-- +goose Down
|
||||||
-- Remove b2b_translations for this scope
|
-- Remove b2b_translations for this scope
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ INSERT IGNORE INTO b2b_components (id, name) VALUES (304, 'products');
|
|||||||
INSERT IGNORE INTO b2b_components (id, name) VALUES (305, 'nav');
|
INSERT IGNORE INTO b2b_components (id, name) VALUES (305, 'nav');
|
||||||
|
|
||||||
-- Component: products (component_id = 304)
|
-- Component: products (component_id = 304)
|
||||||
INSERT IGNORE b2b_translations (lang_id, scope_id, component_id, `key`, data) VALUES
|
INSERT b2b_translations (lang_id, scope_id, component_id, `key`, data) VALUES
|
||||||
-- English (lang_id = 1)
|
-- English (lang_id = 1)
|
||||||
(1, 3, 304, 'title', 'Products'),
|
(1, 3, 304, 'title', 'Products'),
|
||||||
(1, 3, 304, 'image', 'Image'),
|
(1, 3, 304, 'image', 'Image'),
|
||||||
@@ -88,10 +88,12 @@ INSERT IGNORE b2b_translations (lang_id, scope_id, component_id, `key`, data) VA
|
|||||||
(3, 3, 304, 'yes', 'Ano'),
|
(3, 3, 304, 'yes', 'Ano'),
|
||||||
(3, 3, 304, 'no', 'Ne'),
|
(3, 3, 304, 'no', 'Ne'),
|
||||||
(3, 3, 304, 'added_to_cart', 'Přidáno do košíku'),
|
(3, 3, 304, 'added_to_cart', 'Přidáno do košíku'),
|
||||||
(3, 3, 304, 'description', 'Popis');
|
(3, 3, 304, 'description', 'Popis')
|
||||||
|
ON DUPLICATE KEY UPDATE
|
||||||
|
data = IF(data IS NULL, VALUES(data), data);
|
||||||
|
|
||||||
-- Component: nav (component_id = 305)
|
-- Component: nav (component_id = 305)
|
||||||
INSERT IGNORE b2b_translations (lang_id, scope_id, component_id, `key`, data) VALUES
|
INSERT b2b_translations (lang_id, scope_id, component_id, `key`, data) VALUES
|
||||||
-- English (lang_id = 1)
|
-- English (lang_id = 1)
|
||||||
(1, 3, 305, 'chart', 'Chart'),
|
(1, 3, 305, 'chart', 'Chart'),
|
||||||
(1, 3, 305, 'products', 'Products'),
|
(1, 3, 305, 'products', 'Products'),
|
||||||
@@ -100,7 +102,9 @@ INSERT IGNORE b2b_translations (lang_id, scope_id, component_id, `key`, data) VA
|
|||||||
(2, 3, 305, 'products', 'Produkty'),
|
(2, 3, 305, 'products', 'Produkty'),
|
||||||
-- Czech (lang_id = 3)
|
-- Czech (lang_id = 3)
|
||||||
(3, 3, 305, 'chart', 'Graf'),
|
(3, 3, 305, 'chart', 'Graf'),
|
||||||
(3, 3, 305, 'products', 'Produkty');
|
(3, 3, 305, 'products', 'Produkty')
|
||||||
|
ON DUPLICATE KEY UPDATE
|
||||||
|
data = IF(data IS NULL, VALUES(data), data);
|
||||||
|
|
||||||
-- +goose Down
|
-- +goose Down
|
||||||
-- Remove translations for these components
|
-- Remove translations for these components
|
||||||
|
|||||||
Reference in New Issue
Block a user