@hed-hog/catalog 0.0.293 → 0.0.294

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +409 -379
  2. package/dist/catalog-resource.config.d.ts.map +1 -1
  3. package/dist/catalog-resource.config.js +51 -24
  4. package/dist/catalog-resource.config.js.map +1 -1
  5. package/dist/catalog.controller.d.ts +420 -0
  6. package/dist/catalog.controller.d.ts.map +1 -1
  7. package/dist/catalog.controller.js +98 -0
  8. package/dist/catalog.controller.js.map +1 -1
  9. package/dist/catalog.module.d.ts.map +1 -1
  10. package/dist/catalog.module.js +5 -1
  11. package/dist/catalog.module.js.map +1 -1
  12. package/dist/catalog.service.d.ts +216 -1
  13. package/dist/catalog.service.d.ts.map +1 -1
  14. package/dist/catalog.service.js +1111 -5
  15. package/dist/catalog.service.js.map +1 -1
  16. package/hedhog/data/catalog_attribute.yaml +202 -0
  17. package/hedhog/data/catalog_attribute_option.yaml +109 -0
  18. package/hedhog/data/catalog_category.yaml +47 -0
  19. package/hedhog/data/catalog_category_attribute.yaml +209 -0
  20. package/hedhog/data/menu.yaml +133 -99
  21. package/hedhog/data/route.yaml +72 -8
  22. package/hedhog/frontend/app/[resource]/page.tsx.ejs +391 -33
  23. package/hedhog/frontend/app/_components/catalog-ai-form-assist-dialog.tsx.ejs +340 -0
  24. package/hedhog/frontend/app/_components/catalog-resource-form-sheet.tsx.ejs +907 -92
  25. package/hedhog/frontend/app/_lib/catalog-resources.tsx.ejs +929 -1161
  26. package/hedhog/frontend/messages/en.json +389 -299
  27. package/hedhog/frontend/messages/pt.json +389 -299
  28. package/hedhog/table/catalog_attribute.yaml +67 -52
  29. package/hedhog/table/catalog_attribute_option.yaml +40 -0
  30. package/hedhog/table/catalog_category.yaml +40 -0
  31. package/hedhog/table/catalog_category_attribute.yaml +37 -31
  32. package/hedhog/table/catalog_comparison.yaml +19 -22
  33. package/hedhog/table/catalog_product.yaml +30 -28
  34. package/hedhog/table/catalog_product_attribute_value.yaml +44 -31
  35. package/hedhog/table/catalog_product_category.yaml +13 -13
  36. package/hedhog/table/catalog_score_criterion.yaml +42 -25
  37. package/hedhog/table/catalog_seo_page_rule.yaml +10 -10
  38. package/hedhog/table/catalog_similarity_rule.yaml +33 -20
  39. package/hedhog/table/catalog_site.yaml +21 -13
  40. package/hedhog/table/catalog_site_category.yaml +12 -12
  41. package/package.json +6 -6
  42. package/src/catalog-resource.config.ts +132 -105
  43. package/src/catalog.controller.ts +91 -24
  44. package/src/catalog.module.ts +16 -12
  45. package/src/catalog.service.ts +1569 -56
@@ -1,21 +1,32 @@
1
- columns:
2
- - type: pk
3
- - name: category_id
4
- type: fk
5
- references:
6
- table: category
7
- column: id
8
- onDelete: CASCADE
9
- - name: rule_type
10
- type: enum
11
- values: [same_category, compatible_category, shared_attribute, manual]
12
- default: same_category
13
- - name: rule_json
14
- type: json
15
- isNullable: true
16
- - name: min_similarity_score
17
- type: decimal
18
- precision: 5
1
+ columns:
2
+ - type: pk
3
+ - name: catalog_category_id
4
+ type: fk
5
+ references:
6
+ table: catalog_category
7
+ column: id
8
+ onDelete: CASCADE
9
+ - name: related_catalog_category_id
10
+ type: fk
11
+ isNullable: true
12
+ references:
13
+ table: catalog_category
14
+ column: id
15
+ onDelete: SET NULL
16
+ - name: shared_attribute_id
17
+ type: fk
18
+ isNullable: true
19
+ references:
20
+ table: catalog_attribute
21
+ column: id
22
+ onDelete: SET NULL
23
+ - name: rule_type
24
+ type: enum
25
+ values: [same_category, compatible_category, shared_attribute, manual]
26
+ default: same_category
27
+ - name: min_similarity_score
28
+ type: decimal
29
+ precision: 5
19
30
  scale: 2
20
31
  default: 0
21
32
  - name: status
@@ -24,5 +35,7 @@ columns:
24
35
  default: active
25
36
  - type: created_at
26
37
  - type: updated_at
27
- indices:
28
- - columns: [category_id, status]
38
+ indices:
39
+ - columns: [catalog_category_id, status]
40
+ - columns: [related_catalog_category_id, status]
41
+ - columns: [shared_attribute_id, status]
@@ -17,15 +17,22 @@ columns:
17
17
  type: enum
18
18
  values: [portal, niche, tenant]
19
19
  default: portal
20
- - name: default_locale_id
21
- type: fk
22
- references:
23
- table: locale
24
- column: id
25
- onDelete: RESTRICT
26
- - name: theme_settings_json
27
- type: json
28
- isNullable: true
20
+ - name: default_locale_id
21
+ type: fk
22
+ references:
23
+ table: locale
24
+ column: id
25
+ onDelete: RESTRICT
26
+ - name: logo_file_id
27
+ type: fk
28
+ isNullable: true
29
+ references:
30
+ table: file
31
+ column: id
32
+ onDelete: SET NULL
33
+ - name: theme_settings_json
34
+ type: json
35
+ isNullable: true
29
36
  - name: seo_defaults_json
30
37
  type: json
31
38
  isNullable: true
@@ -34,7 +41,8 @@ columns:
34
41
  indices:
35
42
  - columns: [slug]
36
43
  isUnique: true
37
- - columns: [domain]
38
- isUnique: true
39
- - columns: [status]
40
- - columns: [default_locale_id]
44
+ - columns: [domain]
45
+ isUnique: true
46
+ - columns: [status]
47
+ - columns: [default_locale_id]
48
+ - columns: [logo_file_id]
@@ -1,17 +1,17 @@
1
- columns:
2
- - type: pk
3
- - name: site_id
1
+ columns:
2
+ - type: pk
3
+ - name: site_id
4
4
  type: fk
5
5
  references:
6
6
  table: catalog_site
7
7
  column: id
8
8
  onDelete: CASCADE
9
- - name: category_id
10
- type: fk
11
- references:
12
- table: category
13
- column: id
14
- onDelete: CASCADE
9
+ - name: catalog_category_id
10
+ type: fk
11
+ references:
12
+ table: catalog_category
13
+ column: id
14
+ onDelete: CASCADE
15
15
  - name: is_primary
16
16
  type: boolean
17
17
  default: false
@@ -21,6 +21,6 @@ columns:
21
21
  default: visible
22
22
  - type: created_at
23
23
  - type: updated_at
24
- indices:
25
- - columns: [site_id, category_id]
26
- isUnique: true
24
+ indices:
25
+ - columns: [site_id, catalog_category_id]
26
+ isUnique: true
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hed-hog/catalog",
3
- "version": "0.0.293",
3
+ "version": "0.0.294",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "dependencies": {
@@ -10,13 +10,13 @@
10
10
  "@nestjs/jwt": "^11",
11
11
  "@nestjs/mapped-types": "*",
12
12
  "@hed-hog/api": "0.0.4",
13
- "@hed-hog/core": "0.0.293",
14
- "@hed-hog/api-locale": "0.0.13",
15
13
  "@hed-hog/api-pagination": "0.0.6",
16
14
  "@hed-hog/api-prisma": "0.0.5",
17
- "@hed-hog/category": "0.0.293",
18
- "@hed-hog/tag": "0.0.293",
19
- "@hed-hog/content": "0.0.293"
15
+ "@hed-hog/core": "0.0.294",
16
+ "@hed-hog/category": "0.0.294",
17
+ "@hed-hog/api-locale": "0.0.13",
18
+ "@hed-hog/tag": "0.0.294",
19
+ "@hed-hog/content": "0.0.294"
20
20
  },
21
21
  "exports": {
22
22
  ".": {
@@ -1,52 +1,64 @@
1
- export type CatalogResourceConfig = {
2
- resource: string;
3
- model: string;
4
- searchFields: string[];
1
+ export type CatalogResourceConfig = {
2
+ resource: string;
3
+ model: string;
4
+ searchFields: string[];
5
5
  filterFields?: string[];
6
6
  fields: string[];
7
7
  defaultOrderBy?: Record<string, 'asc' | 'desc'>;
8
8
  statusField?: string;
9
9
  activeStatusValue?: string;
10
- };
11
-
12
- export const catalogResources: CatalogResourceConfig[] = [
13
- {
14
- resource: 'brands',
15
- model: 'catalog_brand',
16
- searchFields: ['slug', 'name', 'normalized_name'],
10
+ };
11
+
12
+ export const catalogResources: CatalogResourceConfig[] = [
13
+ {
14
+ resource: 'categories',
15
+ model: 'catalog_category',
16
+ searchFields: ['slug', 'name', 'normalized_name', 'description'],
17
+ filterFields: ['status', 'parent_category_id'],
18
+ fields: ['parent_category_id', 'slug', 'name', 'normalized_name', 'description', 'comparison_enabled', 'status', 'sort_order'],
19
+ defaultOrderBy: { sort_order: 'asc' },
20
+ statusField: 'status',
21
+ activeStatusValue: 'active',
22
+ },
23
+ {
24
+ resource: 'brands',
25
+ model: 'catalog_brand',
26
+ searchFields: ['slug', 'name', 'normalized_name'],
17
27
  filterFields: ['status', 'logo_file_id'],
18
28
  fields: ['slug', 'name', 'normalized_name', 'logo_file_id', 'status', 'website_url'],
19
29
  statusField: 'status',
20
30
  activeStatusValue: 'active',
21
31
  },
22
32
  {
23
- resource: 'sites',
24
- model: 'catalog_site',
25
- searchFields: ['slug', 'name', 'domain'],
26
- filterFields: ['status', 'site_type', 'default_locale_id'],
27
- fields: ['slug', 'name', 'domain', 'status', 'site_type', 'default_locale_id', 'theme_settings_json', 'seo_defaults_json'],
28
- statusField: 'status',
29
- activeStatusValue: 'active',
30
- },
31
- {
32
- resource: 'products',
33
- model: 'catalog_product',
34
- searchFields: ['slug', 'name', 'model_name', 'sku', 'gtin'],
35
- filterFields: ['status', 'comparison_status', 'brand_id', 'category_id', 'primary_content_id', 'is_active'],
36
- fields: ['brand_id', 'category_id', 'primary_content_id', 'slug', 'name', 'short_description', 'description', 'model_name', 'sku', 'gtin', 'status', 'comparison_status', 'release_date', 'spec_snapshot_json', 'comparison_snapshot_json', 'is_active'],
37
- statusField: 'status',
38
- activeStatusValue: 'published',
39
- },
40
- {
41
- resource: 'product-categories',
42
- model: 'catalog_product_category',
43
- searchFields: [],
44
- filterFields: ['product_id', 'category_id', 'is_primary'],
45
- fields: ['product_id', 'category_id', 'is_primary', 'sort_order'],
46
- },
47
- {
48
- resource: 'product-images',
49
- model: 'catalog_product_image',
33
+ resource: 'sites',
34
+ model: 'catalog_site',
35
+ searchFields: ['slug', 'name', 'domain'],
36
+ filterFields: ['status', 'site_type', 'default_locale_id', 'logo_file_id'],
37
+ fields: ['slug', 'name', 'domain', 'status', 'site_type', 'default_locale_id', 'logo_file_id', 'theme_settings_json', 'seo_defaults_json'],
38
+ statusField: 'status',
39
+ activeStatusValue: 'active',
40
+ },
41
+ {
42
+ resource: 'products',
43
+ model: 'catalog_product',
44
+ searchFields: ['slug', 'name', 'model_name', 'sku', 'gtin'],
45
+ filterFields: ['status', 'comparison_status', 'brand_id', 'catalog_category_id', 'primary_content_id', 'is_active'],
46
+ // Snapshot JSON fields remain available for backward-compatible reads,
47
+ // but attribute values are now the primary source of truth.
48
+ fields: ['brand_id', 'catalog_category_id', 'primary_content_id', 'slug', 'name', 'short_description', 'description', 'model_name', 'sku', 'gtin', 'status', 'comparison_status', 'release_date', 'spec_snapshot_json', 'comparison_snapshot_json', 'is_active'],
49
+ statusField: 'status',
50
+ activeStatusValue: 'published',
51
+ },
52
+ {
53
+ resource: 'product-categories',
54
+ model: 'catalog_product_category',
55
+ searchFields: [],
56
+ filterFields: ['product_id', 'catalog_category_id', 'is_primary'],
57
+ fields: ['product_id', 'catalog_category_id', 'is_primary', 'sort_order'],
58
+ },
59
+ {
60
+ resource: 'product-images',
61
+ model: 'catalog_product_image',
50
62
  searchFields: ['alt_text', 'role'],
51
63
  filterFields: ['product_id', 'file_id', 'role', 'is_primary'],
52
64
  fields: ['product_id', 'file_id', 'role', 'sort_order', 'is_primary', 'alt_text'],
@@ -60,45 +72,60 @@ export const catalogResources: CatalogResourceConfig[] = [
60
72
  statusField: 'publication_status',
61
73
  activeStatusValue: 'published',
62
74
  },
63
- {
64
- resource: 'attribute-groups',
65
- model: 'catalog_attribute_group',
75
+ {
76
+ resource: 'attribute-groups',
77
+ model: 'catalog_attribute_group',
66
78
  searchFields: ['slug', 'name'],
67
79
  filterFields: ['status'],
68
80
  fields: ['slug', 'name', 'status'],
69
81
  statusField: 'status',
70
82
  activeStatusValue: 'active',
71
83
  },
72
- {
73
- resource: 'attributes',
74
- model: 'catalog_attribute',
75
- searchFields: ['slug', 'label', 'description', 'unit'],
76
- filterFields: ['group_id', 'data_type', 'comparison_mode', 'is_filterable', 'is_sortable', 'is_required_for_comparison'],
77
- fields: ['group_id', 'slug', 'label', 'description', 'data_type', 'unit', 'comparison_mode', 'is_filterable', 'is_sortable', 'is_required_for_comparison', 'normalization_rule_json', 'display_order'],
78
- },
79
- {
80
- resource: 'category-attributes',
81
- model: 'catalog_category_attribute',
82
- searchFields: [],
83
- filterFields: ['category_id', 'attribute_id', 'is_required', 'is_highlighted', 'facet_mode'],
84
- fields: ['category_id', 'attribute_id', 'is_required', 'is_highlighted', 'display_order', 'weight', 'facet_mode'],
85
- },
86
- {
87
- resource: 'product-attributes',
88
- model: 'catalog_product_attribute_value',
89
- searchFields: ['value_text', 'normalized_text', 'source_type'],
90
- filterFields: ['product_id', 'attribute_id', 'is_verified'],
91
- fields: ['product_id', 'attribute_id', 'value_text', 'value_number', 'value_boolean', 'value_json', 'value_unit', 'normalized_text', 'normalized_number', 'source_type', 'confidence_score', 'is_verified'],
92
- },
93
- {
94
- resource: 'score-criteria',
95
- model: 'catalog_score_criterion',
96
- searchFields: ['slug', 'name'],
97
- filterFields: ['category_id', 'status', 'score_type'],
98
- fields: ['category_id', 'slug', 'name', 'weight', 'score_type', 'formula_json', 'display_order', 'status'],
99
- statusField: 'status',
100
- activeStatusValue: 'active',
101
- },
84
+ {
85
+ resource: 'attributes',
86
+ model: 'catalog_attribute',
87
+ searchFields: ['slug', 'code', 'name', 'description', 'unit', 'group_name'],
88
+ filterFields: ['group_id', 'group_name', 'status', 'data_type', 'comparison_mode', 'is_filterable', 'is_sortable', 'is_comparable', 'is_required_default'],
89
+ fields: ['code', 'group_id', 'group_name', 'slug', 'name', 'description', 'data_type', 'unit', 'comparison_mode', 'is_filterable', 'is_sortable', 'is_comparable', 'is_required_default', 'status', 'display_order'],
90
+ defaultOrderBy: { display_order: 'asc' },
91
+ statusField: 'status',
92
+ activeStatusValue: 'active',
93
+ },
94
+ {
95
+ resource: 'attribute-options',
96
+ model: 'catalog_attribute_option',
97
+ searchFields: ['slug', 'label', 'option_value', 'normalized_value'],
98
+ filterFields: ['attribute_id', 'status', 'is_default'],
99
+ fields: ['attribute_id', 'slug', 'label', 'option_value', 'normalized_value', 'sort_order', 'status', 'is_default'],
100
+ defaultOrderBy: { sort_order: 'asc' },
101
+ statusField: 'status',
102
+ activeStatusValue: 'active',
103
+ },
104
+ {
105
+ resource: 'category-attributes',
106
+ model: 'catalog_category_attribute',
107
+ searchFields: [],
108
+ filterFields: ['catalog_category_id', 'attribute_id', 'is_required', 'is_highlight', 'is_filter_visible', 'is_comparison_visible', 'facet_mode'],
109
+ fields: ['catalog_category_id', 'attribute_id', 'is_required', 'is_highlight', 'is_filter_visible', 'is_comparison_visible', 'sort_order', 'weight', 'facet_mode'],
110
+ defaultOrderBy: { sort_order: 'asc' },
111
+ },
112
+ {
113
+ resource: 'product-attributes',
114
+ model: 'catalog_product_attribute_value',
115
+ searchFields: ['raw_value', 'value_text', 'normalized_value', 'normalized_text', 'source_type'],
116
+ filterFields: ['product_id', 'attribute_id', 'attribute_option_id', 'is_verified'],
117
+ fields: ['product_id', 'attribute_id', 'attribute_option_id', 'value_text', 'value_number', 'value_boolean', 'raw_value', 'value_unit', 'normalized_value', 'normalized_text', 'normalized_number', 'source_type', 'confidence_score', 'is_verified'],
118
+ },
119
+ {
120
+ resource: 'score-criteria',
121
+ model: 'catalog_score_criterion',
122
+ searchFields: ['slug', 'name'],
123
+ filterFields: ['catalog_category_id', 'attribute_id', 'target_option_id', 'status', 'score_type'],
124
+ fields: ['catalog_category_id', 'attribute_id', 'target_option_id', 'slug', 'name', 'weight', 'score_type', 'max_score', 'display_order', 'status'],
125
+ defaultOrderBy: { display_order: 'asc' },
126
+ statusField: 'status',
127
+ activeStatusValue: 'active',
128
+ },
102
129
  {
103
130
  resource: 'product-scores',
104
131
  model: 'catalog_product_score',
@@ -106,15 +133,15 @@ export const catalogResources: CatalogResourceConfig[] = [
106
133
  filterFields: ['product_id', 'criterion_id', 'is_manual_override'],
107
134
  fields: ['product_id', 'criterion_id', 'score_value', 'score_label', 'summary', 'calculation_source', 'is_manual_override'],
108
135
  },
109
- {
110
- resource: 'comparisons',
111
- model: 'catalog_comparison',
112
- searchFields: ['slug', 'title', 'summary', 'verdict', 'eligibility_hash'],
113
- filterFields: ['category_id', 'site_id', 'primary_content_id', 'comparison_type', 'generation_mode', 'status'],
114
- fields: ['category_id', 'site_id', 'primary_content_id', 'slug', 'comparison_type', 'generation_mode', 'status', 'title', 'summary', 'intro', 'verdict', 'spec_snapshot_json', 'eligibility_hash', 'published_at'],
115
- statusField: 'status',
116
- activeStatusValue: 'published',
117
- },
136
+ {
137
+ resource: 'comparisons',
138
+ model: 'catalog_comparison',
139
+ searchFields: ['slug', 'title', 'summary', 'verdict', 'eligibility_hash'],
140
+ filterFields: ['catalog_category_id', 'site_id', 'primary_content_id', 'comparison_type', 'generation_mode', 'status'],
141
+ fields: ['catalog_category_id', 'site_id', 'primary_content_id', 'slug', 'comparison_type', 'generation_mode', 'status', 'title', 'summary', 'intro', 'verdict', 'eligibility_hash', 'published_at'],
142
+ statusField: 'status',
143
+ activeStatusValue: 'published',
144
+ },
118
145
  {
119
146
  resource: 'comparison-items',
120
147
  model: 'catalog_comparison_item',
@@ -129,15 +156,15 @@ export const catalogResources: CatalogResourceConfig[] = [
129
156
  filterFields: ['comparison_id', 'product_id', 'attribute_id', 'highlight_type'],
130
157
  fields: ['comparison_id', 'product_id', 'attribute_id', 'highlight_type', 'title', 'body', 'sort_order'],
131
158
  },
132
- {
133
- resource: 'similarity-rules',
134
- model: 'catalog_similarity_rule',
135
- searchFields: ['rule_type'],
136
- filterFields: ['category_id', 'status'],
137
- fields: ['category_id', 'rule_type', 'rule_json', 'min_similarity_score', 'status'],
138
- statusField: 'status',
139
- activeStatusValue: 'active',
140
- },
159
+ {
160
+ resource: 'similarity-rules',
161
+ model: 'catalog_similarity_rule',
162
+ searchFields: ['rule_type'],
163
+ filterFields: ['catalog_category_id', 'related_catalog_category_id', 'shared_attribute_id', 'status'],
164
+ fields: ['catalog_category_id', 'related_catalog_category_id', 'shared_attribute_id', 'rule_type', 'min_similarity_score', 'status'],
165
+ statusField: 'status',
166
+ activeStatusValue: 'active',
167
+ },
141
168
  {
142
169
  resource: 'merchants',
143
170
  model: 'catalog_merchant',
@@ -177,13 +204,13 @@ export const catalogResources: CatalogResourceConfig[] = [
177
204
  filterFields: ['offer_id', 'product_id', 'site_id', 'comparison_id'],
178
205
  fields: ['offer_id', 'product_id', 'site_id', 'comparison_id', 'clicked_at', 'placement', 'utm_json', 'session_hash', 'referrer_host'],
179
206
  },
180
- {
181
- resource: 'site-categories',
182
- model: 'catalog_site_category',
183
- searchFields: ['visibility_status'],
184
- filterFields: ['site_id', 'category_id', 'is_primary', 'visibility_status'],
185
- fields: ['site_id', 'category_id', 'is_primary', 'visibility_status'],
186
- },
207
+ {
208
+ resource: 'site-categories',
209
+ model: 'catalog_site_category',
210
+ searchFields: ['visibility_status'],
211
+ filterFields: ['site_id', 'catalog_category_id', 'is_primary', 'visibility_status'],
212
+ fields: ['site_id', 'catalog_category_id', 'is_primary', 'visibility_status'],
213
+ },
187
214
  {
188
215
  resource: 'content-relations',
189
216
  model: 'catalog_content_relation',
@@ -191,15 +218,15 @@ export const catalogResources: CatalogResourceConfig[] = [
191
218
  filterFields: ['site_id', 'content_id', 'product_id', 'comparison_id', 'relation_type'],
192
219
  fields: ['site_id', 'content_id', 'product_id', 'comparison_id', 'relation_type', 'sort_order'],
193
220
  },
194
- {
195
- resource: 'seo-rules',
196
- model: 'catalog_seo_page_rule',
197
- searchFields: ['page_type', 'rule_slug', 'canonical_strategy'],
198
- filterFields: ['site_id', 'category_id', 'status', 'page_type'],
199
- fields: ['site_id', 'category_id', 'page_type', 'rule_slug', 'status', 'generation_query_json', 'min_product_count', 'min_attribute_coverage', 'canonical_strategy', 'priority', 'template_json'],
200
- statusField: 'status',
201
- activeStatusValue: 'active',
202
- },
221
+ {
222
+ resource: 'seo-rules',
223
+ model: 'catalog_seo_page_rule',
224
+ searchFields: ['page_type', 'rule_slug', 'canonical_strategy'],
225
+ filterFields: ['site_id', 'catalog_category_id', 'status', 'page_type'],
226
+ fields: ['site_id', 'catalog_category_id', 'page_type', 'rule_slug', 'status', 'generation_query_json', 'min_product_count', 'min_attribute_coverage', 'canonical_strategy', 'priority', 'template_json'],
227
+ statusField: 'status',
228
+ activeStatusValue: 'active',
229
+ },
203
230
  {
204
231
  resource: 'import-sources',
205
232
  model: 'catalog_import_source',
@@ -1,18 +1,20 @@
1
1
  import { Role } from '@hed-hog/api';
2
2
  import { Locale } from '@hed-hog/api-locale';
3
3
  import { Pagination } from '@hed-hog/api-pagination';
4
- import {
5
- Body,
6
- Controller,
7
- Delete,
8
- Get,
9
- Param,
10
- ParseIntPipe,
11
- Patch,
12
- Post,
13
- Query,
14
- } from '@nestjs/common';
15
- import { CatalogService } from './catalog.service';
4
+ import {
5
+ Body,
6
+ Controller,
7
+ Delete,
8
+ Get,
9
+ BadRequestException,
10
+ Param,
11
+ ParseIntPipe,
12
+ Patch,
13
+ Post,
14
+ Put,
15
+ Query,
16
+ } from '@nestjs/common';
17
+ import { CatalogService } from './catalog.service';
16
18
 
17
19
  @Role()
18
20
  @Controller('catalog')
@@ -20,18 +22,83 @@ export class CatalogController {
20
22
  constructor(private readonly catalogService: CatalogService) {}
21
23
 
22
24
  @Get('products/:id/images')
23
- async listProductImages(
24
- @Param('id', ParseIntPipe) id: number,
25
- @Pagination() paginationParams,
26
- @Locale() locale: string,
27
- ) {
28
- return this.catalogService.listProductImages(id, locale, paginationParams);
29
- }
30
-
31
- @Get(':resource/stats')
32
- async stats(@Param('resource') resource: string, @Locale() locale: string) {
33
- return this.catalogService.stats(resource, locale);
34
- }
25
+ async listProductImages(
26
+ @Param('id', ParseIntPipe) id: number,
27
+ @Pagination() paginationParams,
28
+ @Locale() locale: string,
29
+ ) {
30
+ return this.catalogService.listProductImages(id, locale, paginationParams);
31
+ }
32
+
33
+ @Get('categories/tree')
34
+ async categoriesTree() {
35
+ return this.catalogService.getCategoriesTree();
36
+ }
37
+
38
+ @Get('categories/:id/attributes')
39
+ async listCategoryAttributes(@Param('id', ParseIntPipe) id: number) {
40
+ return this.catalogService.listCategoryAttributes(id);
41
+ }
42
+
43
+ @Get('products/:id/attributes')
44
+ async listProductAttributes(@Param('id', ParseIntPipe) id: number) {
45
+ return this.catalogService.listProductAttributes(id);
46
+ }
47
+
48
+ @Get('products/:id/structured')
49
+ async structuredProduct(@Param('id', ParseIntPipe) id: number) {
50
+ return this.catalogService.getProductStructuredPayload(id);
51
+ }
52
+
53
+ @Get('products/:id/comparison-payload')
54
+ async comparisonPayload(@Param('id', ParseIntPipe) id: number) {
55
+ return this.catalogService.getProductComparisonPayload(id);
56
+ }
57
+
58
+ @Post('forms/:resource/ai-assist')
59
+ async generateFormAssistSuggestion(
60
+ @Param('resource') resource: string,
61
+ @Body() body: Record<string, unknown>,
62
+ @Locale() locale: string,
63
+ ) {
64
+ const prompt = String(body.prompt ?? '').trim();
65
+
66
+ if (!prompt) {
67
+ throw new BadRequestException('Prompt is required');
68
+ }
69
+
70
+ return this.catalogService.generateFormAssistSuggestion(resource, {
71
+ prompt,
72
+ current_values:
73
+ body.current_values && typeof body.current_values === 'object'
74
+ ? (body.current_values as Record<string, unknown>)
75
+ : {},
76
+ fields: Array.isArray(body.fields)
77
+ ? (body.fields as Record<string, unknown>[])
78
+ : [],
79
+ current_attribute_values: Array.isArray(body.current_attribute_values)
80
+ ? (body.current_attribute_values as Record<string, unknown>[])
81
+ : [],
82
+ }, locale);
83
+ }
84
+
85
+ @Put('products/:id/attributes')
86
+ async updateProductAttributes(
87
+ @Param('id', ParseIntPipe) id: number,
88
+ @Body('values') values: Record<string, unknown>[],
89
+ ) {
90
+ return this.catalogService.updateProductAttributes(id, values ?? []);
91
+ }
92
+
93
+ @Post('products/:id/materialize-snapshots')
94
+ async materializeProductSnapshots(@Param('id', ParseIntPipe) id: number) {
95
+ return this.catalogService.materializeProductSnapshots(id);
96
+ }
97
+
98
+ @Get(':resource/stats')
99
+ async stats(@Param('resource') resource: string, @Locale() locale: string) {
100
+ return this.catalogService.stats(resource, locale);
101
+ }
35
102
 
36
103
  @Get(':resource/:id')
37
104
  async getById(
@@ -1,12 +1,16 @@
1
- import { PaginationModule } from '@hed-hog/api-pagination';
2
- import { Module, forwardRef } from '@nestjs/common';
3
- import { CatalogController } from './catalog.controller';
4
- import { CatalogService } from './catalog.service';
5
-
6
- @Module({
7
- imports: [forwardRef(() => PaginationModule)],
8
- controllers: [CatalogController],
9
- providers: [CatalogService],
10
- exports: [CatalogService],
11
- })
12
- export class CatalogModule {}
1
+ import { PaginationModule } from '@hed-hog/api-pagination';
2
+ import { AiModule } from '@hed-hog/core';
3
+ import { Module, forwardRef } from '@nestjs/common';
4
+ import { CatalogController } from './catalog.controller';
5
+ import { CatalogService } from './catalog.service';
6
+
7
+ @Module({
8
+ imports: [
9
+ forwardRef(() => PaginationModule),
10
+ forwardRef(() => AiModule),
11
+ ],
12
+ controllers: [CatalogController],
13
+ providers: [CatalogService],
14
+ exports: [CatalogService],
15
+ })
16
+ export class CatalogModule {}