From bb1cdee3f45ba68b9dac28d072ee6086e9b0bd45 Mon Sep 17 00:00:00 2001 From: Yakovenko Valeriia Date: Tue, 24 Mar 2026 16:08:31 +0100 Subject: [PATCH 1/5] fix: paginations --- .../components/customer/PageCustomerData.vue | 169 ++++++++++-------- .../components/customer/PageProductsList.vue | 28 ++- bo/src/views/RepoChartView.vue | 2 +- 3 files changed, 111 insertions(+), 88 deletions(-) diff --git a/bo/src/components/customer/PageCustomerData.vue b/bo/src/components/customer/PageCustomerData.vue index dbe2448..3fb35cc 100644 --- a/bo/src/components/customer/PageCustomerData.vue +++ b/bo/src/components/customer/PageCustomerData.vue @@ -1,89 +1,102 @@ diff --git a/bo/src/components/customer/PageProductsList.vue b/bo/src/components/customer/PageProductsList.vue index d8bacfa..cf26c4c 100644 --- a/bo/src/components/customer/PageProductsList.vue +++ b/bo/src/components/customer/PageProductsList.vue @@ -35,7 +35,7 @@ - product image {{ @@ -49,6 +49,7 @@ +
No products found
@@ -59,17 +60,21 @@ \ No newline at end of file diff --git a/bo/src/views/RepoChartView.vue b/bo/src/views/RepoChartView.vue index 122d31b..ed81c31 100644 --- a/bo/src/views/RepoChartView.vue +++ b/bo/src/views/RepoChartView.vue @@ -238,7 +238,7 @@ const columns = computed[]>(() => [
- +
-- 2.49.1 From e570297011908301cd3caed2674047ff8800daae Mon Sep 17 00:00:00 2001 From: Yakovenko Valeriia Date: Tue, 24 Mar 2026 16:13:27 +0100 Subject: [PATCH 2/5] fix: paginations --- bo/src/components/customer/PageProductsList.vue | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bo/src/components/customer/PageProductsList.vue b/bo/src/components/customer/PageProductsList.vue index cf26c4c..62630b4 100644 --- a/bo/src/components/customer/PageProductsList.vue +++ b/bo/src/components/customer/PageProductsList.vue @@ -49,7 +49,9 @@ - +
+ +
No products found
-- 2.49.1 From c13365916cd24d0bafa6df048240833fc5944192 Mon Sep 17 00:00:00 2001 From: Marek Goc Date: Tue, 24 Mar 2026 19:16:02 +0100 Subject: [PATCH 3/5] filters --- .env | 3 +- app/api/openapi.json | 4 +- app/config/config.go | 10 +++ app/delivery/middleware/cors.go | 16 ++++- .../web/api/restricted/listProducts.go | 62 +++++++++++++++++++ app/delivery/web/general/swagger.go | 5 ++ 6 files changed, 95 insertions(+), 5 deletions(-) diff --git a/.env b/.env index 6dcd355..bc5aa8c 100644 --- a/.env +++ b/.env @@ -56,4 +56,5 @@ PDF_SERVER_URL=http://localhost:8000 FILE_MAAL_PL_USER=git_operator FILE_MAAL_PL_PASSWORD=1FnwqcEgIUjQHjt1 -IMAGE_PREFIX=https://www.naluconcept.com # remove prefix to serv them from same host as presta \ No newline at end of file +IMAGE_PREFIX=https://www.naluconcept.com # remove prefix to serv them from same host as presta +CORS_ORGIN=https://www.naluconcept.com \ No newline at end of file diff --git a/app/api/openapi.json b/app/api/openapi.json index 37420fe..b01e771 100644 --- a/app/api/openapi.json +++ b/app/api/openapi.json @@ -11,8 +11,8 @@ }, "servers": [ { - "url": "http://localhost:3000", - "description": "Development server" + "url": "/", + "description": "Development server on same host" } ], "tags": [ diff --git a/app/config/config.go b/app/config/config.go index e5be538..f68d2fd 100644 --- a/app/config/config.go +++ b/app/config/config.go @@ -23,6 +23,7 @@ type Config struct { Pdf PdfPrinter GoogleTranslate GoogleTranslateConfig Image ImageConfig + Cors CorsConfig } type I18n struct { @@ -33,6 +34,10 @@ type ServerConfig struct { Host string `env:"SERVER_HOST,0.0.0.0"` } +type CorsConfig struct { + Origins []string `env:"CORS_ORGIN"` +} + type ImageConfig struct { ImagePrefix string `env:"IMAGE_PREFIX"` } @@ -176,6 +181,11 @@ func load() *Config { if err != nil { slog.Error("not possible to load env variables for google translate : ", err.Error(), "") } + + err = loadEnv(&cfg.Cors) + if err != nil { + slog.Error("not possible to load env variables for google translate : ", err.Error(), "") + } return cfg } diff --git a/app/delivery/middleware/cors.go b/app/delivery/middleware/cors.go index e52024b..f876769 100644 --- a/app/delivery/middleware/cors.go +++ b/app/delivery/middleware/cors.go @@ -1,11 +1,23 @@ package middleware -import "github.com/gofiber/fiber/v3" +import ( + "strings" + + "git.ma-al.com/goc_daniel/b2b/app/config" + "github.com/gofiber/fiber/v3" +) // CORSMiddleware creates CORS middleware func CORSMiddleware() fiber.Handler { return func(c fiber.Ctx) error { - c.Set("Access-Control-Allow-Origin", "*") + + if strings.Contains(c.Get("Host"), "localhost") { + c.Set("Access-Control-Allow-Origin", c.Get("Host")) + } else { + origins := strings.Join(config.Get().Cors.Origins, ",") + c.Set("Access-Control-Allow-Origin", origins) + } + c.Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") c.Set("Access-Control-Allow-Headers", "Content-Type, Authorization") diff --git a/app/delivery/web/api/restricted/listProducts.go b/app/delivery/web/api/restricted/listProducts.go index 2478cc5..5f7b868 100644 --- a/app/delivery/web/api/restricted/listProducts.go +++ b/app/delivery/web/api/restricted/listProducts.go @@ -1,6 +1,8 @@ package restricted import ( + "fmt" + "git.ma-al.com/goc_daniel/b2b/app/config" "git.ma-al.com/goc_daniel/b2b/app/service/listProductsService" "git.ma-al.com/goc_daniel/b2b/app/utils/i18n" @@ -36,6 +38,64 @@ func ListProductsHandlerRoutes(r fiber.Router) fiber.Router { return r } +// var columnMapping map[string]string = map[string]string{ +// "product_id": "id", +// "price": "price_taxed", +// "name": "name", +// "category_id": "category_id", +// "feature_id": "feature_id", +// "feature": "feature_name", +// "value_id": "value_id", +// "value": "value_name", +// "status": "active_sale", +// "stock": "in_stock", +// } + +// type FeatVal = map[uint][]uint + +// func featureValueFilters(feats FeatVal) filters.Filter { +// filt := func(db *gorm.DB) *gorm.DB { +// return db.Where("value_id IN ?", lo.Flatten(lo.Values(feats))).Group("id").Having("COUNT(id) = ?", len(lo.Keys(feats))) +// } +// return filters.NewFilter(filters.FEAT_VAL_PRODUCT_FILTER, filt) +// } + +// func ParseProductFilters(c fiber.Ctx) (find.Paging, *filters.FiltersList, error) { +// var p find.Paging +// fl := filters.NewFiltersList() +// productFilters := new(model.ProductFilters) +// fmt.Printf("fl: %v\n", fl) +// err := c.Bind().Query(productFilters) +// if err != nil { +// return p, &fl, err +// } + +// if productFilters.Name != "" { +// fl.Append(filters.Where("name LIKE ?", fmt.Sprintf("%%%s%%", productFilters.Name))) +// } + +// if productFilters.Sort != "" { +// ord, err := query_params.ParseOrdering[model.Product](c, columnMapping) +// if err != nil { +// return p, &fl, err +// } +// for _, o := range ord { +// fl.Append(filters.Order(o.Column, o.IsDesc)) +// } +// } + +// if len(productFilters.Features) > 0 { +// fl.Append(featureValueFilters(productFilters.Features)) +// } + +// fl.Append(query_params.ParseWhereScopes[model.Product](c, []string{"name"}, columnMapping)...) + +// pageNum, pageElems := query_params.ParsePagination(c) +// p = find.Paging{Page: pageNum, Elements: pageElems} + +// return p, &fl, nil +// } + func (h *ListProductsHandler) GetListing(c fiber.Ctx) error { paging, filters, err := ParseProductFilters(c) if err != nil { @@ -64,6 +124,8 @@ func ParseProductFilters(c fiber.Ctx) (find.Paging, *filters.FiltersList, error) var p find.Paging fl := filters.NewFiltersList() + fmt.Printf("fl: %v\n", fl.All()) + pageNum, pageElems := query_params.ParsePagination(c) p = find.Paging{Page: pageNum, Elements: pageElems} diff --git a/app/delivery/web/general/swagger.go b/app/delivery/web/general/swagger.go index 9e0876c..e0a5cc0 100644 --- a/app/delivery/web/general/swagger.go +++ b/app/delivery/web/general/swagger.go @@ -47,6 +47,11 @@ var swaggerHTML = ` url: "/openapi.json", dom_id: '#swagger-ui', deepLinking: true, + withCredentials: true, + "servers": [ + { "url": "http://localhost:3000" }, + { "url": "http://localhost:5173" } + ], presets: [ SwaggerUIBundle.presets.apis, SwaggerUIStandalonePreset -- 2.49.1 From 0f21ed1f81928ece68ec0a881f77e0ecb19afde5 Mon Sep 17 00:00:00 2001 From: Marek Goc Date: Wed, 25 Mar 2026 02:23:12 +0100 Subject: [PATCH 4/5] filters --- app/api/openapi.json | 71 +++++++++++++++- .../web/api/restricted/listProducts.go | 84 ++----------------- app/model/product.go | 13 +-- .../listProductsRepo/listProductsRepo.go | 67 ++++++++------- app/utils/query/filters/filters.go | 31 ++++++- .../query_params/where_scope_from_query.go | 4 +- 6 files changed, 151 insertions(+), 119 deletions(-) diff --git a/app/api/openapi.json b/app/api/openapi.json index b01e771..a1207e4 100644 --- a/app/api/openapi.json +++ b/app/api/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "b2b API", - "description": "Authentication, user management, and repository time tracking API", + "description": "Authentication, user management, and repository time tracking API\n\n## Filter Operators\nAll filter parameters support the following operators by adding a suffix to the parameter name:\n\n| Suffix | Operator | Example |\n|--------|----------|---------|\n| `_eq` | equals | `product_id_eq=12` |\n| `_neq` | not equals | `active_neq=0` |\n| `_gt` | greater than | `price_gt=100` |\n| `_gte` | greater than or equal | `price_gte=10` |\n| `_lt` | less than | `price_lt=500` |\n| `_lte` | less than or equal | `price_lte=500` |\n| `_in` | IN (comma-separated) | `product_id_in=1,2,3,4` |\n\n## Special Filters\n\n| Parameter | Example | Description |\n|-----------|---------|-------------|\n| `name=~text` | `name=~gold` | LIKE search (case-insensitive partial match) |\n| `price=[min,max]` | `price=[100,500]` | BETWEEN range (inclusive) |\n| `sort=field,direction` | `sort=price,desc` | ORDER BY clause (direction: asc/desc, default: desc) |\n| `p` | `p=1` | Page number (1-based, default: 1) |\n| `elems` | `elems=30` | Elements per page (max: 100, default: 30) |", "version": "1.0.0", "contact": { "name": "API Support", @@ -1043,7 +1043,7 @@ "get": { "tags": ["Products"], "summary": "Get product listing", - "description": "Returns a paginated list of products with their basic information. Requires authentication.", + "description": "Returns a paginated list of products with their basic information. Supports filtering via query parameters with operators (e.g., product_id_eq=12, name=~gold). Use sort parameter for ordering. Requires authentication.", "operationId": "getProductListing", "security": [ { @@ -1064,12 +1064,77 @@ { "name": "elems", "in": "query", - "description": "Number of items per page", + "description": "Number of items per page (max: 100, default: 30)", "required": false, "schema": { "type": "integer", "default": 30 } + }, + { + "name": "sort", + "in": "query", + "description": "Sort field and direction. Format: field,direction (e.g., 'product_id,desc' or 'name,asc')", + "required": false, + "schema": { + "type": "string", + "example": "product_id,desc" + } + }, + { + "name": "product_id", + "in": "query", + "description": "Filter by product ID. Use suffix _eq, _gt, _gte, _lt, _lte, _neq, _in for operators.", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "name": "name", + "in": "query", + "description": "Filter by product name using LIKE (case-insensitive). Use ~ prefix for partial match (e.g., '~gold')", + "required": false, + "schema": { + "type": "string", + "example": "~gold" + } + }, + { + "name": "reference", + "in": "query", + "description": "Filter by product reference using LIKE (case-insensitive)", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "id_category", + "in": "query", + "description": "Filter by category ID. Use suffix _eq, _gt, _gte, _lt, _lte, _neq, _in for operators.", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "name": "category_name", + "in": "query", + "description": "Filter by category name using LIKE (case-insensitive)", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "quantity", + "in": "query", + "description": "Filter by quantity. Use suffix _eq, _gt, _gte, _lt, _lte for operators.", + "required": false, + "schema": { + "type": "integer" + } } ], "responses": { diff --git a/app/delivery/web/api/restricted/listProducts.go b/app/delivery/web/api/restricted/listProducts.go index 5f7b868..962fd07 100644 --- a/app/delivery/web/api/restricted/listProducts.go +++ b/app/delivery/web/api/restricted/listProducts.go @@ -1,14 +1,11 @@ package restricted import ( - "fmt" - "git.ma-al.com/goc_daniel/b2b/app/config" + "git.ma-al.com/goc_daniel/b2b/app/model" "git.ma-al.com/goc_daniel/b2b/app/service/listProductsService" "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/query/filters" - "git.ma-al.com/goc_daniel/b2b/app/utils/query/find" "git.ma-al.com/goc_daniel/b2b/app/utils/query/query_params" "git.ma-al.com/goc_daniel/b2b/app/utils/response" "git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors" @@ -38,66 +35,8 @@ func ListProductsHandlerRoutes(r fiber.Router) fiber.Router { return r } -// var columnMapping map[string]string = map[string]string{ -// "product_id": "id", -// "price": "price_taxed", -// "name": "name", -// "category_id": "category_id", -// "feature_id": "feature_id", -// "feature": "feature_name", -// "value_id": "value_id", -// "value": "value_name", -// "status": "active_sale", -// "stock": "in_stock", -// } - -// type FeatVal = map[uint][]uint - -// func featureValueFilters(feats FeatVal) filters.Filter { -// filt := func(db *gorm.DB) *gorm.DB { -// return db.Where("value_id IN ?", lo.Flatten(lo.Values(feats))).Group("id").Having("COUNT(id) = ?", len(lo.Keys(feats))) -// } -// return filters.NewFilter(filters.FEAT_VAL_PRODUCT_FILTER, filt) -// } - -// func ParseProductFilters(c fiber.Ctx) (find.Paging, *filters.FiltersList, error) { -// var p find.Paging -// fl := filters.NewFiltersList() -// productFilters := new(model.ProductFilters) -// fmt.Printf("fl: %v\n", fl) -// err := c.Bind().Query(productFilters) -// if err != nil { -// return p, &fl, err -// } - -// if productFilters.Name != "" { -// fl.Append(filters.Where("name LIKE ?", fmt.Sprintf("%%%s%%", productFilters.Name))) -// } - -// if productFilters.Sort != "" { -// ord, err := query_params.ParseOrdering[model.Product](c, columnMapping) -// if err != nil { -// return p, &fl, err -// } -// for _, o := range ord { -// fl.Append(filters.Order(o.Column, o.IsDesc)) -// } -// } - -// if len(productFilters.Features) > 0 { -// fl.Append(featureValueFilters(productFilters.Features)) -// } - -// fl.Append(query_params.ParseWhereScopes[model.Product](c, []string{"name"}, columnMapping)...) - -// pageNum, pageElems := query_params.ParsePagination(c) -// p = find.Paging{Page: pageNum, Elements: pageElems} - -// return p, &fl, nil -// } - func (h *ListProductsHandler) GetListing(c fiber.Ctx) error { - paging, filters, err := ParseProductFilters(c) + paging, filters, err := query_params.ParseFilters[model.Product](c, columnMappingGetListing) if err != nil { return c.Status(responseErrors.GetErrorStatus(err)). JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err))) @@ -118,16 +57,11 @@ func (h *ListProductsHandler) GetListing(c fiber.Ctx) error { return c.JSON(response.Make(&listing.Items, int(listing.Count), i18n.T_(c, response.Message_OK))) } -var columnMapping map[string]string = map[string]string{} - -func ParseProductFilters(c fiber.Ctx) (find.Paging, *filters.FiltersList, error) { - var p find.Paging - fl := filters.NewFiltersList() - - fmt.Printf("fl: %v\n", fl.All()) - - pageNum, pageElems := query_params.ParsePagination(c) - p = find.Paging{Page: pageNum, Elements: pageElems} - - return p, &fl, nil +var columnMappingGetListing map[string]string = map[string]string{ + "product_id": "ps.id_product", + "name": "pl.name", + "reference": "ps.reference", + "category_name": "cl.name", + "id_category": "cp.id_category", + "quantity": "sa.quantity", } diff --git a/app/model/product.go b/app/model/product.go index 36b0d70..c0b6331 100644 --- a/app/model/product.go +++ b/app/model/product.go @@ -62,11 +62,14 @@ type Product struct { DeliveryDays uint `gorm:"column:delivery_days" json:"delivery_days" form:"delivery_days"` } type ProductInList struct { - ProductID uint `gorm:"column:product_id;primaryKey" json:"product_id" form:"product_id"` - Name string `gorm:"column:name" json:"name" form:"name"` - ImageID string `gorm:"column:id_image"` - LinkRewrite string `gorm:"column:link_rewrite"` - Active uint `gorm:"column:active" json:"active" form:"active"` + ProductID uint `gorm:"column:product_id" json:"product_id" form:"product_id"` + Name string `gorm:"column:name" json:"name" form:"name"` + LinkRewrite string `gorm:"column:link_rewrite" json:"link_rewrite"` + ImageLink string `gorm:"column:image_link" json:"image_link"` + CategoryName string `gorm:"column:category_name" json:"category_name" form:"category_name"` + Reference string `gorm:"column:reference" json:"reference"` + VariantsNumber uint `gorm:"column:variants_number" json:"variants_number"` + Quantity uint `gorm:"column:quantity" json:"quantity"` } type ProductFilters struct { diff --git a/app/repos/listProductsRepo/listProductsRepo.go b/app/repos/listProductsRepo/listProductsRepo.go index bd9fda3..32739d9 100644 --- a/app/repos/listProductsRepo/listProductsRepo.go +++ b/app/repos/listProductsRepo/listProductsRepo.go @@ -1,10 +1,8 @@ package listProductsRepo import ( - "git.ma-al.com/goc_daniel/b2b/app/config" "git.ma-al.com/goc_daniel/b2b/app/db" "git.ma-al.com/goc_daniel/b2b/app/model" - constdata "git.ma-al.com/goc_daniel/b2b/app/utils/const_data" "git.ma-al.com/goc_daniel/b2b/app/utils/query/filters" "git.ma-al.com/goc_daniel/b2b/app/utils/query/find" ) @@ -23,43 +21,44 @@ func (repo *ListProductsRepo) GetListing(id_lang uint, p find.Paging, filt *filt var listing []model.ProductInList var total int64 - subQuery := db.DB. - Table("ps_image"). - Select("id_product, MIN(id_image) AS id_image"). - Group("id_product") - - err := db.DB. - Table("ps_product"). + query := db.Get(). + Table("ps_product_shop AS ps"). Select(` - ps_product.id_product AS product_id, - ps_product_lang.name AS name, - ps_product.active AS active, - ps_product_lang.link_rewrite AS link_rewrite, - COALESCE(CONCAT( ?, '/', ps_image_shop.id_image, '-small_default/', ps_product_lang.link_rewrite, '.webp'), CONCAT( ?, '/', any_image.id_image, '-small_default/', ps_product_lang.link_rewrite, '.webp')) AS id_image - `, config.Get().Image.ImagePrefix, config.Get().Image.ImagePrefix). - Joins(` - LEFT JOIN ps_product_lang - ON ps_product_lang.id_product = ps_product.id_product - AND ps_product_lang.id_shop = ? - AND ps_product_lang.id_lang = ? - `, constdata.SHOP_ID, id_lang). - Joins(` - LEFT JOIN ps_image_shop - ON ps_image_shop.id_product = ps_product.id_product - AND ps_image_shop.id_shop = ? - AND ps_image_shop.cover = 1 - `, constdata.SHOP_ID). - Joins("LEFT JOIN (?) AS any_image ON ps_product.id_product = any_image.id_product", subQuery). - Limit(p.Limit()). - Offset(p.Offset()). - Scan(&listing).Error + ps.id_product AS product_id, + pl.name AS name, + pl.link_rewrite AS link_rewrite, + CONCAT('https://www.naluconcept.com', '/', ims.id_image, '-small_default/', pl.link_rewrite, '.webp') AS image_link, + cl.name AS category_name, + p.reference AS reference, + COUNT(DISTINCT pas.id_product_attribute) AS variants_number, + sa.quantity AS quantity + `). + Joins("JOIN ps_product p ON p.id_product = ps.id_product"). + Joins("JOIN ps_category_product cp ON ps.id_product = cp.id_product"). + Joins("JOIN ps_product_lang pl ON pl.id_product = ps.id_product AND pl.id_lang = ?", id_lang). + Joins("JOIN ps_image_shop ims ON ims.id_product = ps.id_product AND ims.cover = 1"). + Joins("JOIN ps_category_lang cl ON cl.id_category = ps.id_category_default AND cl.id_lang = ?", id_lang). + Joins("LEFT JOIN ps_product_attribute_shop pas ON pas.id_product = cp.id_product"). + Joins("LEFT JOIN ps_stock_available sa ON sa.id_product = ps.id_product"). + Where("ps.active = ?", 1). + Group("cp.id_product") + + // Apply all filters + if filt != nil { + filt.ApplyAll(query) + } + + // run counter first as query is without limit and offset + err := query.Count(&total).Error if err != nil { return find.Found[model.ProductInList]{}, err } - err = db.DB. - Table("ps_product"). - Count(&total).Error + err = query. + Order("ps.id_product DESC"). + Limit(p.Limit()). + Offset(p.Offset()). + Scan(&listing).Error if err != nil { return find.Found[model.ProductInList]{}, err } diff --git a/app/utils/query/filters/filters.go b/app/utils/query/filters/filters.go index 3f7a26d..f818406 100644 --- a/app/utils/query/filters/filters.go +++ b/app/utils/query/filters/filters.go @@ -55,7 +55,9 @@ func WhereFromStrings(column, conditionOperator, value string) Filter { value = strings.ReplaceAll(value, "~", "") filt = func(d *gorm.DB) *gorm.DB { - return d.Where("lower("+column+`) LIKE lower(?)`, "%"+value+"%") + // return d.Where("lower("+column+`) LIKE lower(?)`, "%"+value+"%") + // (jeśli masz collation case-insensitive, np. utf8mb4_general_ci) + return d.Where(column+` LIKE ?`, "%"+value+"%") } @@ -65,6 +67,33 @@ func WhereFromStrings(column, conditionOperator, value string) Filter { } } + // Handle IN operator for comma-separated values (e.g., product_id_in=1,2,3,4) + if conditionOperator == "IN" { + parts := strings.Split(value, ",") + var values []interface{} + for _, p := range parts { + p = strings.TrimSpace(p) + if p == "" { + continue + } + // Try to parse as int first + if i, err := strconv.ParseInt(p, 10, 64); err == nil { + values = append(values, i) + } else if f, err := strconv.ParseFloat(p, 64); err == nil { + values = append(values, f) + } else { + values = append(values, p) + } + } + filt = func(d *gorm.DB) *gorm.DB { + return d.Where(column+" IN ?", values) + } + return Filter{ + category: WHERE_FILTER, + filter: filt, + } + } + if strings.Contains(value, "]") && strings.Contains(value, "[") { period := strings.ReplaceAll(value, "[", "") period = strings.ReplaceAll(period, "]", "") diff --git a/app/utils/query/query_params/where_scope_from_query.go b/app/utils/query/query_params/where_scope_from_query.go index be4de92..fe8d992 100644 --- a/app/utils/query/query_params/where_scope_from_query.go +++ b/app/utils/query/query_params/where_scope_from_query.go @@ -46,7 +46,7 @@ func ParseWhereScopes[T any](c fiber.Ctx, ignoredKeys []string, formColumnMappin } func extractOperator(key string) (base string, operatorSuffix string) { - suffixes := []string{"_gt", "_gte", "_lt", "_lte", "_eq", "_neq"} + suffixes := []string{"_gt", "_gte", "_lt", "_lte", "_eq", "_neq", "_in"} for _, suf := range suffixes { if strings.HasSuffix(key, suf) { return strings.TrimSuffix(key, suf), suf[1:] @@ -69,6 +69,8 @@ func resolveOperator(suffix string) string { return "!=" case "eq": return "=" + case "in": + return "IN" default: return "LIKE" } -- 2.49.1 From 62aaa2316485976521cd8757479f612f138f2c14 Mon Sep 17 00:00:00 2001 From: Marek Goc Date: Tue, 24 Mar 2026 13:09:12 +0100 Subject: [PATCH 5/5] update openapi --- .env_example | 59 +++++++++++++++++++ app/model/countries.go | 13 ++++ app/repos/categoriesRepo/categoriesRepo.go | 22 +++++-- .../localeSelectorRepo/localeSelectorRepo.go | 6 +- 4 files changed, 91 insertions(+), 9 deletions(-) create mode 100644 .env_example diff --git a/.env_example b/.env_example new file mode 100644 index 0000000..6dcd355 --- /dev/null +++ b/.env_example @@ -0,0 +1,59 @@ +SERVER_PORT=3000 +SERVER_HOST=0.0.0.0 + +# Database Configuration +DB_HOST=localhost +DB_PORT=3306 +DB_USER=root +DB_PASSWORD=Maal12345678 +DB_NAME=nalu +PROJECT_NAME=nalu_b2b +DB_SERVICE_NAME=nalu_b2b +DB_SSLMODE=disable + +# App COnfig +APP_NAME="B2b Management System" +APP_VERSION=2.1.0 +APP_ENVIRONMENT=development + +# JWT Configuration +AUTH_JWT_SECRET=5c020e6ed3d8d6e67e5804d67c83c4bd5ae474df749af6d63d8f20e7e2ba29b3 +AUTH_JWT_EXPIRATION=86400 +AUTH_REFRESH_EXPIRATION=604800 + +# Meili search +MEILISEARCH_URL=http://localhost:7700 +MEILISEARCH_API_KEY=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 + +# OpenAI +OPENAI_KEY=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 + +# Google Translate Client +GOOGLE_APPLICATION_CREDENTIALS=./google-cred.json +GOOGLE_CLOUD_PROJECT_ID=translation-343517 + +# Google OAuth2 +OAUTH_GOOGLE_CLIENT_ID=331979954218-9vrpe08oqhhcgj6bvu6d4lds0dt630m9.apps.googleusercontent.com +OAUTH_GOOGLE_CLIENT_SECRET=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 +OAUTH_GOOGLE_REDIRECT_URL=http://localhost:3000/api/v1/public/auth/google/callback + +# Email Configuration (SMTP) +# Set EMAIL_ENABLED=true to require email verification +EMAIL_ENABLED=true +EMAIL_SMTP_HOST=mail.ma-al.com +EMAIL_SMTP_PORT=587 +EMAIL_SMTP_USER=test@ma-al.com +EMAIL_SMTP_PASSWORD=maal12345678 +EMAIL_FROM=test@ma-al.com +EMAIL_FROM_NAME=Gitea Manager +EMAIL_ADMIN=goc_marek@ma-al.pl + +I18N_LANGS=en,pl,cs + +PDF_SERVER_URL=http://localhost:8000 + + +FILE_MAAL_PL_USER=git_operator +FILE_MAAL_PL_PASSWORD=1FnwqcEgIUjQHjt1 + +IMAGE_PREFIX=https://www.naluconcept.com # remove prefix to serv them from same host as presta \ No newline at end of file diff --git a/app/model/countries.go b/app/model/countries.go index 61972d2..dccbe8b 100644 --- a/app/model/countries.go +++ b/app/model/countries.go @@ -9,3 +9,16 @@ type Country struct { CurrencyISOCode string `gorm:"column:iso_code" json:"currency_iso_code"` CurrencyName string `gorm:"column:name" json:"currency_name"` } + +func (Country) TableName() string { + return "b2b_countries" +} + +type PSCountry struct { + ID uint `gorm:"primaryKey;column:id_country" json:"id"` + CurrencyID uint `gorm:"column:id_currency" json:"currency_id"` +} + +func (PSCountry) TableName() string { + return "ps_country" +} diff --git a/app/repos/categoriesRepo/categoriesRepo.go b/app/repos/categoriesRepo/categoriesRepo.go index d420187..bc378c1 100644 --- a/app/repos/categoriesRepo/categoriesRepo.go +++ b/app/repos/categoriesRepo/categoriesRepo.go @@ -30,12 +30,22 @@ func (repo *CategoriesRepo) GetAllCategories(id_lang uint) ([]model.ScannedCateg ps_category.is_root_category AS is_root_category, ps_category_lang.link_rewrite AS link_rewrite, ps_lang.iso_code AS iso_code - FROM ps_category - LEFT JOIN ps_category_lang ON ps_category_lang.id_category = ps_category.id_category AND ps_category_lang.id_shop = ? AND ps_category_lang.id_lang = ? - LEFT JOIN ps_category_shop ON ps_category_shop.id_category = ps_category.id_category AND ps_category_shop.id_shop = ? - JOIN ps_lang ON ps_lang.id_lang = ps_category_lang.id_lang - `, - constdata.SHOP_ID, id_lang, constdata.SHOP_ID). + `). + Joins(` + LEFT JOIN ps_category_lang + ON ps_category_lang.id_category = ps_category.id_category + AND ps_category_lang.id_shop = ? + AND ps_category_lang.id_lang = ? + `, constdata.SHOP_ID, id_lang). + Joins(` + LEFT JOIN ps_category_shop + ON ps_category_shop.id_category = ps_category.id_category + AND ps_category_shop.id_shop = ? + `, constdata.SHOP_ID). + Joins(` + JOIN ps_lang + ON ps_lang.id_lang = ps_category_lang.id_lang + `). Scan(&allCategories).Error return allCategories, err diff --git a/app/repos/localeSelectorRepo/localeSelectorRepo.go b/app/repos/localeSelectorRepo/localeSelectorRepo.go index f08a57b..d52ef87 100644 --- a/app/repos/localeSelectorRepo/localeSelectorRepo.go +++ b/app/repos/localeSelectorRepo/localeSelectorRepo.go @@ -27,9 +27,9 @@ func (repo *LocaleSelectorRepo) GetLanguages() ([]model.Language, error) { func (repo *LocaleSelectorRepo) GetCountriesAndCurrencies() ([]model.Country, error) { var countries []model.Country - err := db.DB.Table("b2b_countries"). - Select("b2b_countries.id, b2b_countries.name, b2b_countries.flag, ps_currency.id as id_currency, ps_currency.name as currency_name, ps_currency.iso_code as currency_iso_code"). - Joins("JOIN ps_currency ON ps_currency.id = b2b_countries.currency"). + err := db.DB. + Select("b2b_countries.id, b2b_countries.name, b2b_countries.flag, ps_currency.id_currency as id_currency, ps_currency.name as currency_name, ps_currency.iso_code as currency_iso_code"). + Joins("JOIN ps_currency ON ps_currency.id_currency = b2b_countries.currency"). Scan(&countries).Error return countries, err -- 2.49.1