@hed-hog/catalog 0.0.295 → 0.0.297
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.
- package/README.md +409 -409
- package/dist/catalog.controller.d.ts +4 -4
- package/dist/catalog.service.d.ts +4 -4
- package/hedhog/data/catalog_attribute.yaml +202 -202
- package/hedhog/data/catalog_attribute_option.yaml +109 -109
- package/hedhog/data/catalog_category.yaml +47 -47
- package/hedhog/data/catalog_category_attribute.yaml +209 -209
- package/hedhog/data/menu.yaml +133 -133
- package/hedhog/data/role.yaml +7 -7
- package/hedhog/data/route.yaml +72 -72
- package/hedhog/frontend/app/[resource]/page.tsx.ejs +391 -391
- package/hedhog/frontend/app/_components/catalog-ai-form-assist-dialog.tsx.ejs +340 -340
- package/hedhog/frontend/app/_components/catalog-resource-form-sheet.tsx.ejs +907 -907
- package/hedhog/frontend/app/_lib/catalog-resources.tsx.ejs +929 -929
- package/hedhog/frontend/messages/en.json +389 -389
- package/hedhog/frontend/messages/pt.json +389 -389
- package/hedhog/table/catalog_affiliate_program.yaml +41 -41
- package/hedhog/table/catalog_attribute.yaml +67 -67
- package/hedhog/table/catalog_attribute_group.yaml +18 -18
- package/hedhog/table/catalog_attribute_option.yaml +40 -40
- package/hedhog/table/catalog_brand.yaml +34 -34
- package/hedhog/table/catalog_category.yaml +40 -40
- package/hedhog/table/catalog_category_attribute.yaml +37 -37
- package/hedhog/table/catalog_click_event.yaml +50 -50
- package/hedhog/table/catalog_comparison.yaml +19 -19
- package/hedhog/table/catalog_comparison_highlight.yaml +39 -39
- package/hedhog/table/catalog_comparison_item.yaml +30 -30
- package/hedhog/table/catalog_content_relation.yaml +42 -42
- package/hedhog/table/catalog_import_run.yaml +33 -33
- package/hedhog/table/catalog_import_source.yaml +24 -24
- package/hedhog/table/catalog_merchant.yaml +29 -29
- package/hedhog/table/catalog_offer.yaml +83 -83
- package/hedhog/table/catalog_price_history.yaml +34 -34
- package/hedhog/table/catalog_product.yaml +30 -30
- package/hedhog/table/catalog_product_attribute_value.yaml +44 -44
- package/hedhog/table/catalog_product_category.yaml +13 -13
- package/hedhog/table/catalog_product_image.yaml +34 -34
- package/hedhog/table/catalog_product_score.yaml +38 -38
- package/hedhog/table/catalog_product_site.yaml +47 -47
- package/hedhog/table/catalog_product_tag.yaml +19 -19
- package/hedhog/table/catalog_score_criterion.yaml +42 -42
- package/hedhog/table/catalog_seo_page_rule.yaml +10 -10
- package/hedhog/table/catalog_similarity_rule.yaml +33 -33
- package/hedhog/table/catalog_site.yaml +21 -21
- package/hedhog/table/catalog_site_category.yaml +12 -12
- package/package.json +10 -10
- package/src/catalog-resource.config.ts +132 -132
- package/src/catalog.controller.ts +91 -91
- package/src/catalog.module.ts +16 -16
- package/src/catalog.service.ts +3 -3
- package/src/index.ts +1 -1
- package/src/language/en.json +4 -4
- package/src/language/pt.json +4 -4
package/README.md
CHANGED
|
@@ -1,409 +1,409 @@
|
|
|
1
|
-
# @hed-hog/catalog
|
|
2
|
-
|
|
3
|
-
## Overview
|
|
4
|
-
|
|
5
|
-
`@hed-hog/catalog` is the central catalog module for HedHog. It manages:
|
|
6
|
-
|
|
7
|
-
- catalog categories
|
|
8
|
-
- brands, merchants, sites, offers, and products
|
|
9
|
-
- structured technical attributes by category
|
|
10
|
-
- product attribute values with typed storage
|
|
11
|
-
- product images and comparison-oriented payloads
|
|
12
|
-
|
|
13
|
-
The module keeps the existing generic CRUD backbone, pagination, locale, roles, Prisma integration, and admin experience. The main architectural change is that structured attribute tables are now the primary source of truth for technical metadata, while legacy JSON snapshots remain secondary and derived.
|
|
14
|
-
|
|
15
|
-
## Core architecture
|
|
16
|
-
|
|
17
|
-
### Primary domain entities
|
|
18
|
-
|
|
19
|
-
- `catalog_category`
|
|
20
|
-
Product taxonomy used by the catalog itself.
|
|
21
|
-
- `catalog_attribute`
|
|
22
|
-
Master definition of a technical/comparable attribute.
|
|
23
|
-
- `catalog_attribute_option`
|
|
24
|
-
Enumerated values for `option` attributes.
|
|
25
|
-
- `catalog_category_attribute`
|
|
26
|
-
Rules that define which attributes belong to a category and how they behave in UI/comparison.
|
|
27
|
-
- `catalog_product_attribute_value`
|
|
28
|
-
Typed value storage for product attributes.
|
|
29
|
-
- `catalog_product`
|
|
30
|
-
Main product record with brand, category, content, status, and secondary snapshots.
|
|
31
|
-
|
|
32
|
-
### Supporting entities
|
|
33
|
-
|
|
34
|
-
- `catalog_brand`
|
|
35
|
-
Supports `logo_file_id` via the shared `file` module.
|
|
36
|
-
- `catalog_site`
|
|
37
|
-
Supports `logo_file_id` and site-specific JSON settings.
|
|
38
|
-
- `catalog_merchant`
|
|
39
|
-
Supports `logo_file_id` when merchant branding is needed.
|
|
40
|
-
- `catalog_product_image`
|
|
41
|
-
Product gallery/media records via `file_id`.
|
|
42
|
-
- `catalog_offer`, `catalog_affiliate_program`, `catalog_product_site`, `catalog_comparison`, and related tables
|
|
43
|
-
Commercial/publication/comparison layers that build on top of products and attributes.
|
|
44
|
-
|
|
45
|
-
## Structured attributes vs JSON snapshots
|
|
46
|
-
|
|
47
|
-
### Primary data
|
|
48
|
-
|
|
49
|
-
The canonical source for technical product data is:
|
|
50
|
-
|
|
51
|
-
1. `catalog_category`
|
|
52
|
-
2. `catalog_attribute`
|
|
53
|
-
3. `catalog_category_attribute`
|
|
54
|
-
4. `catalog_product_attribute_value`
|
|
55
|
-
|
|
56
|
-
This is what enables:
|
|
57
|
-
|
|
58
|
-
- consistent admin forms
|
|
59
|
-
- typed validation
|
|
60
|
-
- category-aware required fields
|
|
61
|
-
- filters and sorting by normalized values
|
|
62
|
-
- comparison payload generation
|
|
63
|
-
|
|
64
|
-
### Secondary data
|
|
65
|
-
|
|
66
|
-
`catalog_product.spec_snapshot_json` and `catalog_product.comparison_snapshot_json` are still present for compatibility and fast reads.
|
|
67
|
-
|
|
68
|
-
Important rules:
|
|
69
|
-
|
|
70
|
-
- they are **not** the primary source of truth anymore
|
|
71
|
-
- they should be treated as **derived/cache fields**
|
|
72
|
-
- they are materialized from structured attributes
|
|
73
|
-
- old data should not be deleted without a migration plan
|
|
74
|
-
|
|
75
|
-
The service now exposes snapshot materialization helpers and keeps snapshots warm after attribute updates.
|
|
76
|
-
|
|
77
|
-
## Main tables
|
|
78
|
-
|
|
79
|
-
### `catalog_category`
|
|
80
|
-
|
|
81
|
-
Suggested use:
|
|
82
|
-
|
|
83
|
-
- create taxonomy nodes like `gpu`, `cpu`, `motherboard`, `ram`, `ssd`, `monitor`
|
|
84
|
-
- optionally organize them with `parent_category_id`
|
|
85
|
-
- control whether the category participates in comparison with `comparison_enabled`
|
|
86
|
-
|
|
87
|
-
Key fields:
|
|
88
|
-
|
|
89
|
-
- `slug`
|
|
90
|
-
- `name`
|
|
91
|
-
- `description`
|
|
92
|
-
- `parent_category_id`
|
|
93
|
-
- `comparison_enabled`
|
|
94
|
-
- `status`
|
|
95
|
-
- `sort_order`
|
|
96
|
-
|
|
97
|
-
### `catalog_attribute`
|
|
98
|
-
|
|
99
|
-
Defines the technical vocabulary shared across categories.
|
|
100
|
-
|
|
101
|
-
Key fields:
|
|
102
|
-
|
|
103
|
-
- `code`
|
|
104
|
-
- `slug`
|
|
105
|
-
- `name`
|
|
106
|
-
- `description`
|
|
107
|
-
- `data_type`
|
|
108
|
-
- `unit`
|
|
109
|
-
- `group_name`
|
|
110
|
-
- `comparison_mode`
|
|
111
|
-
- `is_filterable`
|
|
112
|
-
- `is_sortable`
|
|
113
|
-
- `is_comparable`
|
|
114
|
-
- `is_required_default`
|
|
115
|
-
- `status`
|
|
116
|
-
- `display_order`
|
|
117
|
-
|
|
118
|
-
Supported `data_type` values:
|
|
119
|
-
|
|
120
|
-
- `text`
|
|
121
|
-
- `long_text`
|
|
122
|
-
- `number`
|
|
123
|
-
- `boolean`
|
|
124
|
-
- `option`
|
|
125
|
-
|
|
126
|
-
### `catalog_attribute_option`
|
|
127
|
-
|
|
128
|
-
Used when `catalog_attribute.data_type = option`.
|
|
129
|
-
|
|
130
|
-
Key fields:
|
|
131
|
-
|
|
132
|
-
- `attribute_id`
|
|
133
|
-
- `slug`
|
|
134
|
-
- `label`
|
|
135
|
-
- `option_value`
|
|
136
|
-
- `normalized_value`
|
|
137
|
-
- `sort_order`
|
|
138
|
-
- `status`
|
|
139
|
-
- `is_default`
|
|
140
|
-
|
|
141
|
-
### `catalog_category_attribute`
|
|
142
|
-
|
|
143
|
-
Binds attributes to categories and controls behavior.
|
|
144
|
-
|
|
145
|
-
Key fields:
|
|
146
|
-
|
|
147
|
-
- `catalog_category_id`
|
|
148
|
-
- `attribute_id`
|
|
149
|
-
- `is_required`
|
|
150
|
-
- `is_highlight`
|
|
151
|
-
- `is_filter_visible`
|
|
152
|
-
- `is_comparison_visible`
|
|
153
|
-
- `sort_order`
|
|
154
|
-
- `weight`
|
|
155
|
-
- `facet_mode`
|
|
156
|
-
|
|
157
|
-
### `catalog_product_attribute_value`
|
|
158
|
-
|
|
159
|
-
Stores one typed value per `product_id + attribute_id`.
|
|
160
|
-
|
|
161
|
-
Key fields:
|
|
162
|
-
|
|
163
|
-
- `product_id`
|
|
164
|
-
- `attribute_id`
|
|
165
|
-
- `attribute_option_id`
|
|
166
|
-
- `value_text`
|
|
167
|
-
- `value_number`
|
|
168
|
-
- `value_boolean`
|
|
169
|
-
- `raw_value`
|
|
170
|
-
- `value_unit`
|
|
171
|
-
- `normalized_value`
|
|
172
|
-
- `normalized_text`
|
|
173
|
-
- `normalized_number`
|
|
174
|
-
- `source_type`
|
|
175
|
-
- `confidence_score`
|
|
176
|
-
- `is_verified`
|
|
177
|
-
|
|
178
|
-
## Attribute value rules
|
|
179
|
-
|
|
180
|
-
Only one value channel should be used per attribute value row:
|
|
181
|
-
|
|
182
|
-
- `text` and `long_text` use `value_text`
|
|
183
|
-
- `number` uses `value_number`
|
|
184
|
-
- `boolean` uses `value_boolean`
|
|
185
|
-
- `option` uses `attribute_option_id`
|
|
186
|
-
|
|
187
|
-
The backend validates this and rejects inconsistent payloads.
|
|
188
|
-
|
|
189
|
-
## Initial catalog seeds
|
|
190
|
-
|
|
191
|
-
The module now ships declarative YAML seeds in `libraries/catalog/hedhog/data`.
|
|
192
|
-
|
|
193
|
-
Seeded categories:
|
|
194
|
-
|
|
195
|
-
- `gpu`
|
|
196
|
-
- `cpu`
|
|
197
|
-
- `motherboard`
|
|
198
|
-
- `ram`
|
|
199
|
-
- `ssd`
|
|
200
|
-
- `monitor`
|
|
201
|
-
|
|
202
|
-
Seeded example attributes:
|
|
203
|
-
|
|
204
|
-
### GPU
|
|
205
|
-
|
|
206
|
-
- `chipset`
|
|
207
|
-
- `vram_gb`
|
|
208
|
-
- `memory_type`
|
|
209
|
-
- `memory_bus_bits`
|
|
210
|
-
- `boost_clock_mhz`
|
|
211
|
-
- `tdp_w`
|
|
212
|
-
- `length_mm`
|
|
213
|
-
- `ray_tracing`
|
|
214
|
-
|
|
215
|
-
### CPU
|
|
216
|
-
|
|
217
|
-
- `cores`
|
|
218
|
-
- `threads`
|
|
219
|
-
- `base_clock_ghz`
|
|
220
|
-
- `boost_clock_ghz`
|
|
221
|
-
- `socket`
|
|
222
|
-
- `tdp_w`
|
|
223
|
-
- `integrated_graphics`
|
|
224
|
-
|
|
225
|
-
Seeded example options:
|
|
226
|
-
|
|
227
|
-
- GPU memory types like `GDDR6`, `GDDR6X`, `GDDR7`, `HBM3`, `HBM3E`
|
|
228
|
-
- CPU sockets like `AM4`, `AM5`, `LGA1700`, `LGA1851`, `sTR5`
|
|
229
|
-
|
|
230
|
-
## Admin resources
|
|
231
|
-
|
|
232
|
-
The catalog admin supports generic CRUD plus specialized product attribute editing.
|
|
233
|
-
|
|
234
|
-
Main resources:
|
|
235
|
-
|
|
236
|
-
- `categories`
|
|
237
|
-
- `brands`
|
|
238
|
-
- `sites`
|
|
239
|
-
- `products`
|
|
240
|
-
- `attribute-groups`
|
|
241
|
-
- `attributes`
|
|
242
|
-
- `attribute-options`
|
|
243
|
-
- `category-attributes`
|
|
244
|
-
- `product-attributes`
|
|
245
|
-
- `comparisons`
|
|
246
|
-
- `offers`
|
|
247
|
-
- `merchants`
|
|
248
|
-
- `affiliate-programs`
|
|
249
|
-
- `seo-rules`
|
|
250
|
-
- `import-sources`
|
|
251
|
-
|
|
252
|
-
### Product admin experience
|
|
253
|
-
|
|
254
|
-
When editing a product:
|
|
255
|
-
|
|
256
|
-
1. select `catalog_category_id`
|
|
257
|
-
2. the admin loads category attributes dynamically
|
|
258
|
-
3. fields are grouped by attribute group or `group_name`
|
|
259
|
-
4. typed inputs are rendered for `text`, `long_text`, `number`, `boolean`, and `option`
|
|
260
|
-
5. required fields are validated before save
|
|
261
|
-
6. values are persisted into `catalog_product_attribute_value`
|
|
262
|
-
|
|
263
|
-
The product screen also shows:
|
|
264
|
-
|
|
265
|
-
- brand
|
|
266
|
-
- category
|
|
267
|
-
- status and active flag
|
|
268
|
-
- grouped technical attributes
|
|
269
|
-
- product images as a secondary block
|
|
270
|
-
- snapshots as secondary/derived data
|
|
271
|
-
|
|
272
|
-
## Logos and files
|
|
273
|
-
|
|
274
|
-
The module uses the shared `file` flow already present in the monorepo.
|
|
275
|
-
|
|
276
|
-
### Brand logo
|
|
277
|
-
|
|
278
|
-
Use `catalog_brand.logo_file_id`.
|
|
279
|
-
|
|
280
|
-
### Site logo
|
|
281
|
-
|
|
282
|
-
Use `catalog_site.logo_file_id`.
|
|
283
|
-
|
|
284
|
-
### Merchant logo
|
|
285
|
-
|
|
286
|
-
Use `catalog_merchant.logo_file_id`.
|
|
287
|
-
|
|
288
|
-
### Product gallery
|
|
289
|
-
|
|
290
|
-
Use `catalog_product_image.file_id`.
|
|
291
|
-
|
|
292
|
-
No custom upload mechanism was introduced. The admin uses the same file upload pattern already used across the project.
|
|
293
|
-
|
|
294
|
-
## Runtime helpers
|
|
295
|
-
|
|
296
|
-
`CatalogService` now includes helpers for:
|
|
297
|
-
|
|
298
|
-
- listing category attributes
|
|
299
|
-
- listing product attributes
|
|
300
|
-
- building a structured product payload
|
|
301
|
-
- building a comparison payload from structured attributes
|
|
302
|
-
- materializing `spec_snapshot_json` and `comparison_snapshot_json`
|
|
303
|
-
|
|
304
|
-
Relevant endpoints:
|
|
305
|
-
|
|
306
|
-
- `GET /catalog/categories/tree`
|
|
307
|
-
- `GET /catalog/categories/:id/attributes`
|
|
308
|
-
- `GET /catalog/products/:id/attributes`
|
|
309
|
-
- `PUT /catalog/products/:id/attributes`
|
|
310
|
-
- `GET /catalog/products/:id/structured`
|
|
311
|
-
- `GET /catalog/products/:id/comparison-payload`
|
|
312
|
-
- `POST /catalog/products/:id/materialize-snapshots`
|
|
313
|
-
- generic CRUD under `/catalog/:resource`
|
|
314
|
-
|
|
315
|
-
## Safe transition strategy
|
|
316
|
-
|
|
317
|
-
The module preserves snapshot fields to avoid destructive migration behavior.
|
|
318
|
-
|
|
319
|
-
Current strategy:
|
|
320
|
-
|
|
321
|
-
- structured attribute tables are canonical
|
|
322
|
-
- snapshots remain readable
|
|
323
|
-
- snapshots are regenerated from structured values
|
|
324
|
-
- attribute updates trigger snapshot materialization
|
|
325
|
-
- compatibility aliases are still accepted in a few payloads where the old UI/model used different names
|
|
326
|
-
|
|
327
|
-
This keeps the module safe for gradual adoption while avoiding JSON-first modeling going forward.
|
|
328
|
-
|
|
329
|
-
## How to apply schema and seeds
|
|
330
|
-
|
|
331
|
-
This repository follows a database-first HedHog workflow.
|
|
332
|
-
|
|
333
|
-
### Schema/data apply
|
|
334
|
-
|
|
335
|
-
```powershell
|
|
336
|
-
hedhog dev apply
|
|
337
|
-
pnpm db:update
|
|
338
|
-
```
|
|
339
|
-
|
|
340
|
-
`hedhog dev apply` applies table YAML and the declarative data under `hedhog/data`, so the catalog seed data lives in the same standard workflow as the rest of the project.
|
|
341
|
-
|
|
342
|
-
### Build checks
|
|
343
|
-
|
|
344
|
-
```powershell
|
|
345
|
-
pnpm --filter api build
|
|
346
|
-
pnpm --filter @hed-hog/catalog build
|
|
347
|
-
pnpm --filter admin build
|
|
348
|
-
```
|
|
349
|
-
|
|
350
|
-
## Example flows
|
|
351
|
-
|
|
352
|
-
### 1. Create a brand with logo
|
|
353
|
-
|
|
354
|
-
1. Open `Catalog > Brands`
|
|
355
|
-
2. Create a brand with `name`, `slug`, and optional `website_url`
|
|
356
|
-
3. Upload the logo through the shared upload field
|
|
357
|
-
4. Save, which stores the uploaded file in `logo_file_id`
|
|
358
|
-
|
|
359
|
-
### 2. Create a category
|
|
360
|
-
|
|
361
|
-
1. Open `Catalog > Categories`
|
|
362
|
-
2. Create `gpu`-like or business-specific categories
|
|
363
|
-
3. Configure `comparison_enabled`, hierarchy, and status
|
|
364
|
-
|
|
365
|
-
### 3. Create an attribute
|
|
366
|
-
|
|
367
|
-
1. Open `Catalog > Attributes`
|
|
368
|
-
2. Define `code`, `slug`, `name`, `data_type`, and `group_name`
|
|
369
|
-
3. Mark whether the attribute is filterable/sortable/comparable
|
|
370
|
-
|
|
371
|
-
### 4. Link an attribute to a category
|
|
372
|
-
|
|
373
|
-
1. Open `Catalog > Category Attributes`
|
|
374
|
-
2. Pick the category and the attribute
|
|
375
|
-
3. Configure required/highlight/filter/comparison behavior
|
|
376
|
-
4. Save ordering and facet mode
|
|
377
|
-
|
|
378
|
-
### 5. Create a product
|
|
379
|
-
|
|
380
|
-
1. Open `Catalog > Products`
|
|
381
|
-
2. Fill the base product fields
|
|
382
|
-
3. Choose brand and category
|
|
383
|
-
4. Save the product
|
|
384
|
-
|
|
385
|
-
### 6. Save product technical attributes
|
|
386
|
-
|
|
387
|
-
1. In the same product sheet, after category selection, the technical fields load automatically
|
|
388
|
-
2. Fill typed values
|
|
389
|
-
3. Save the product
|
|
390
|
-
4. The admin persists values to `catalog_product_attribute_value`
|
|
391
|
-
5. Snapshots are materialized as derived JSON
|
|
392
|
-
|
|
393
|
-
## Starting with GPU and CPU
|
|
394
|
-
|
|
395
|
-
The seeded data is enough to bootstrap a real first catalog slice:
|
|
396
|
-
|
|
397
|
-
1. create brands like `NVIDIA`, `AMD`, `Intel`
|
|
398
|
-
2. create products under `gpu` and `cpu`
|
|
399
|
-
3. fill structured attributes
|
|
400
|
-
4. optionally attach product images
|
|
401
|
-
5. use comparison payload endpoints to power future storefront or editorial flows
|
|
402
|
-
|
|
403
|
-
## Future improvements
|
|
404
|
-
|
|
405
|
-
- automatic snapshot materialization on more product lifecycle events
|
|
406
|
-
- richer import pipelines that map external payloads directly to structured attributes
|
|
407
|
-
- category-specific admin presets and product templates
|
|
408
|
-
- storefront queries optimized for faceting and comparison at scale
|
|
409
|
-
- deeper read models for SEO, rankings, and recommendation features
|
|
1
|
+
# @hed-hog/catalog
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
`@hed-hog/catalog` is the central catalog module for HedHog. It manages:
|
|
6
|
+
|
|
7
|
+
- catalog categories
|
|
8
|
+
- brands, merchants, sites, offers, and products
|
|
9
|
+
- structured technical attributes by category
|
|
10
|
+
- product attribute values with typed storage
|
|
11
|
+
- product images and comparison-oriented payloads
|
|
12
|
+
|
|
13
|
+
The module keeps the existing generic CRUD backbone, pagination, locale, roles, Prisma integration, and admin experience. The main architectural change is that structured attribute tables are now the primary source of truth for technical metadata, while legacy JSON snapshots remain secondary and derived.
|
|
14
|
+
|
|
15
|
+
## Core architecture
|
|
16
|
+
|
|
17
|
+
### Primary domain entities
|
|
18
|
+
|
|
19
|
+
- `catalog_category`
|
|
20
|
+
Product taxonomy used by the catalog itself.
|
|
21
|
+
- `catalog_attribute`
|
|
22
|
+
Master definition of a technical/comparable attribute.
|
|
23
|
+
- `catalog_attribute_option`
|
|
24
|
+
Enumerated values for `option` attributes.
|
|
25
|
+
- `catalog_category_attribute`
|
|
26
|
+
Rules that define which attributes belong to a category and how they behave in UI/comparison.
|
|
27
|
+
- `catalog_product_attribute_value`
|
|
28
|
+
Typed value storage for product attributes.
|
|
29
|
+
- `catalog_product`
|
|
30
|
+
Main product record with brand, category, content, status, and secondary snapshots.
|
|
31
|
+
|
|
32
|
+
### Supporting entities
|
|
33
|
+
|
|
34
|
+
- `catalog_brand`
|
|
35
|
+
Supports `logo_file_id` via the shared `file` module.
|
|
36
|
+
- `catalog_site`
|
|
37
|
+
Supports `logo_file_id` and site-specific JSON settings.
|
|
38
|
+
- `catalog_merchant`
|
|
39
|
+
Supports `logo_file_id` when merchant branding is needed.
|
|
40
|
+
- `catalog_product_image`
|
|
41
|
+
Product gallery/media records via `file_id`.
|
|
42
|
+
- `catalog_offer`, `catalog_affiliate_program`, `catalog_product_site`, `catalog_comparison`, and related tables
|
|
43
|
+
Commercial/publication/comparison layers that build on top of products and attributes.
|
|
44
|
+
|
|
45
|
+
## Structured attributes vs JSON snapshots
|
|
46
|
+
|
|
47
|
+
### Primary data
|
|
48
|
+
|
|
49
|
+
The canonical source for technical product data is:
|
|
50
|
+
|
|
51
|
+
1. `catalog_category`
|
|
52
|
+
2. `catalog_attribute`
|
|
53
|
+
3. `catalog_category_attribute`
|
|
54
|
+
4. `catalog_product_attribute_value`
|
|
55
|
+
|
|
56
|
+
This is what enables:
|
|
57
|
+
|
|
58
|
+
- consistent admin forms
|
|
59
|
+
- typed validation
|
|
60
|
+
- category-aware required fields
|
|
61
|
+
- filters and sorting by normalized values
|
|
62
|
+
- comparison payload generation
|
|
63
|
+
|
|
64
|
+
### Secondary data
|
|
65
|
+
|
|
66
|
+
`catalog_product.spec_snapshot_json` and `catalog_product.comparison_snapshot_json` are still present for compatibility and fast reads.
|
|
67
|
+
|
|
68
|
+
Important rules:
|
|
69
|
+
|
|
70
|
+
- they are **not** the primary source of truth anymore
|
|
71
|
+
- they should be treated as **derived/cache fields**
|
|
72
|
+
- they are materialized from structured attributes
|
|
73
|
+
- old data should not be deleted without a migration plan
|
|
74
|
+
|
|
75
|
+
The service now exposes snapshot materialization helpers and keeps snapshots warm after attribute updates.
|
|
76
|
+
|
|
77
|
+
## Main tables
|
|
78
|
+
|
|
79
|
+
### `catalog_category`
|
|
80
|
+
|
|
81
|
+
Suggested use:
|
|
82
|
+
|
|
83
|
+
- create taxonomy nodes like `gpu`, `cpu`, `motherboard`, `ram`, `ssd`, `monitor`
|
|
84
|
+
- optionally organize them with `parent_category_id`
|
|
85
|
+
- control whether the category participates in comparison with `comparison_enabled`
|
|
86
|
+
|
|
87
|
+
Key fields:
|
|
88
|
+
|
|
89
|
+
- `slug`
|
|
90
|
+
- `name`
|
|
91
|
+
- `description`
|
|
92
|
+
- `parent_category_id`
|
|
93
|
+
- `comparison_enabled`
|
|
94
|
+
- `status`
|
|
95
|
+
- `sort_order`
|
|
96
|
+
|
|
97
|
+
### `catalog_attribute`
|
|
98
|
+
|
|
99
|
+
Defines the technical vocabulary shared across categories.
|
|
100
|
+
|
|
101
|
+
Key fields:
|
|
102
|
+
|
|
103
|
+
- `code`
|
|
104
|
+
- `slug`
|
|
105
|
+
- `name`
|
|
106
|
+
- `description`
|
|
107
|
+
- `data_type`
|
|
108
|
+
- `unit`
|
|
109
|
+
- `group_name`
|
|
110
|
+
- `comparison_mode`
|
|
111
|
+
- `is_filterable`
|
|
112
|
+
- `is_sortable`
|
|
113
|
+
- `is_comparable`
|
|
114
|
+
- `is_required_default`
|
|
115
|
+
- `status`
|
|
116
|
+
- `display_order`
|
|
117
|
+
|
|
118
|
+
Supported `data_type` values:
|
|
119
|
+
|
|
120
|
+
- `text`
|
|
121
|
+
- `long_text`
|
|
122
|
+
- `number`
|
|
123
|
+
- `boolean`
|
|
124
|
+
- `option`
|
|
125
|
+
|
|
126
|
+
### `catalog_attribute_option`
|
|
127
|
+
|
|
128
|
+
Used when `catalog_attribute.data_type = option`.
|
|
129
|
+
|
|
130
|
+
Key fields:
|
|
131
|
+
|
|
132
|
+
- `attribute_id`
|
|
133
|
+
- `slug`
|
|
134
|
+
- `label`
|
|
135
|
+
- `option_value`
|
|
136
|
+
- `normalized_value`
|
|
137
|
+
- `sort_order`
|
|
138
|
+
- `status`
|
|
139
|
+
- `is_default`
|
|
140
|
+
|
|
141
|
+
### `catalog_category_attribute`
|
|
142
|
+
|
|
143
|
+
Binds attributes to categories and controls behavior.
|
|
144
|
+
|
|
145
|
+
Key fields:
|
|
146
|
+
|
|
147
|
+
- `catalog_category_id`
|
|
148
|
+
- `attribute_id`
|
|
149
|
+
- `is_required`
|
|
150
|
+
- `is_highlight`
|
|
151
|
+
- `is_filter_visible`
|
|
152
|
+
- `is_comparison_visible`
|
|
153
|
+
- `sort_order`
|
|
154
|
+
- `weight`
|
|
155
|
+
- `facet_mode`
|
|
156
|
+
|
|
157
|
+
### `catalog_product_attribute_value`
|
|
158
|
+
|
|
159
|
+
Stores one typed value per `product_id + attribute_id`.
|
|
160
|
+
|
|
161
|
+
Key fields:
|
|
162
|
+
|
|
163
|
+
- `product_id`
|
|
164
|
+
- `attribute_id`
|
|
165
|
+
- `attribute_option_id`
|
|
166
|
+
- `value_text`
|
|
167
|
+
- `value_number`
|
|
168
|
+
- `value_boolean`
|
|
169
|
+
- `raw_value`
|
|
170
|
+
- `value_unit`
|
|
171
|
+
- `normalized_value`
|
|
172
|
+
- `normalized_text`
|
|
173
|
+
- `normalized_number`
|
|
174
|
+
- `source_type`
|
|
175
|
+
- `confidence_score`
|
|
176
|
+
- `is_verified`
|
|
177
|
+
|
|
178
|
+
## Attribute value rules
|
|
179
|
+
|
|
180
|
+
Only one value channel should be used per attribute value row:
|
|
181
|
+
|
|
182
|
+
- `text` and `long_text` use `value_text`
|
|
183
|
+
- `number` uses `value_number`
|
|
184
|
+
- `boolean` uses `value_boolean`
|
|
185
|
+
- `option` uses `attribute_option_id`
|
|
186
|
+
|
|
187
|
+
The backend validates this and rejects inconsistent payloads.
|
|
188
|
+
|
|
189
|
+
## Initial catalog seeds
|
|
190
|
+
|
|
191
|
+
The module now ships declarative YAML seeds in `libraries/catalog/hedhog/data`.
|
|
192
|
+
|
|
193
|
+
Seeded categories:
|
|
194
|
+
|
|
195
|
+
- `gpu`
|
|
196
|
+
- `cpu`
|
|
197
|
+
- `motherboard`
|
|
198
|
+
- `ram`
|
|
199
|
+
- `ssd`
|
|
200
|
+
- `monitor`
|
|
201
|
+
|
|
202
|
+
Seeded example attributes:
|
|
203
|
+
|
|
204
|
+
### GPU
|
|
205
|
+
|
|
206
|
+
- `chipset`
|
|
207
|
+
- `vram_gb`
|
|
208
|
+
- `memory_type`
|
|
209
|
+
- `memory_bus_bits`
|
|
210
|
+
- `boost_clock_mhz`
|
|
211
|
+
- `tdp_w`
|
|
212
|
+
- `length_mm`
|
|
213
|
+
- `ray_tracing`
|
|
214
|
+
|
|
215
|
+
### CPU
|
|
216
|
+
|
|
217
|
+
- `cores`
|
|
218
|
+
- `threads`
|
|
219
|
+
- `base_clock_ghz`
|
|
220
|
+
- `boost_clock_ghz`
|
|
221
|
+
- `socket`
|
|
222
|
+
- `tdp_w`
|
|
223
|
+
- `integrated_graphics`
|
|
224
|
+
|
|
225
|
+
Seeded example options:
|
|
226
|
+
|
|
227
|
+
- GPU memory types like `GDDR6`, `GDDR6X`, `GDDR7`, `HBM3`, `HBM3E`
|
|
228
|
+
- CPU sockets like `AM4`, `AM5`, `LGA1700`, `LGA1851`, `sTR5`
|
|
229
|
+
|
|
230
|
+
## Admin resources
|
|
231
|
+
|
|
232
|
+
The catalog admin supports generic CRUD plus specialized product attribute editing.
|
|
233
|
+
|
|
234
|
+
Main resources:
|
|
235
|
+
|
|
236
|
+
- `categories`
|
|
237
|
+
- `brands`
|
|
238
|
+
- `sites`
|
|
239
|
+
- `products`
|
|
240
|
+
- `attribute-groups`
|
|
241
|
+
- `attributes`
|
|
242
|
+
- `attribute-options`
|
|
243
|
+
- `category-attributes`
|
|
244
|
+
- `product-attributes`
|
|
245
|
+
- `comparisons`
|
|
246
|
+
- `offers`
|
|
247
|
+
- `merchants`
|
|
248
|
+
- `affiliate-programs`
|
|
249
|
+
- `seo-rules`
|
|
250
|
+
- `import-sources`
|
|
251
|
+
|
|
252
|
+
### Product admin experience
|
|
253
|
+
|
|
254
|
+
When editing a product:
|
|
255
|
+
|
|
256
|
+
1. select `catalog_category_id`
|
|
257
|
+
2. the admin loads category attributes dynamically
|
|
258
|
+
3. fields are grouped by attribute group or `group_name`
|
|
259
|
+
4. typed inputs are rendered for `text`, `long_text`, `number`, `boolean`, and `option`
|
|
260
|
+
5. required fields are validated before save
|
|
261
|
+
6. values are persisted into `catalog_product_attribute_value`
|
|
262
|
+
|
|
263
|
+
The product screen also shows:
|
|
264
|
+
|
|
265
|
+
- brand
|
|
266
|
+
- category
|
|
267
|
+
- status and active flag
|
|
268
|
+
- grouped technical attributes
|
|
269
|
+
- product images as a secondary block
|
|
270
|
+
- snapshots as secondary/derived data
|
|
271
|
+
|
|
272
|
+
## Logos and files
|
|
273
|
+
|
|
274
|
+
The module uses the shared `file` flow already present in the monorepo.
|
|
275
|
+
|
|
276
|
+
### Brand logo
|
|
277
|
+
|
|
278
|
+
Use `catalog_brand.logo_file_id`.
|
|
279
|
+
|
|
280
|
+
### Site logo
|
|
281
|
+
|
|
282
|
+
Use `catalog_site.logo_file_id`.
|
|
283
|
+
|
|
284
|
+
### Merchant logo
|
|
285
|
+
|
|
286
|
+
Use `catalog_merchant.logo_file_id`.
|
|
287
|
+
|
|
288
|
+
### Product gallery
|
|
289
|
+
|
|
290
|
+
Use `catalog_product_image.file_id`.
|
|
291
|
+
|
|
292
|
+
No custom upload mechanism was introduced. The admin uses the same file upload pattern already used across the project.
|
|
293
|
+
|
|
294
|
+
## Runtime helpers
|
|
295
|
+
|
|
296
|
+
`CatalogService` now includes helpers for:
|
|
297
|
+
|
|
298
|
+
- listing category attributes
|
|
299
|
+
- listing product attributes
|
|
300
|
+
- building a structured product payload
|
|
301
|
+
- building a comparison payload from structured attributes
|
|
302
|
+
- materializing `spec_snapshot_json` and `comparison_snapshot_json`
|
|
303
|
+
|
|
304
|
+
Relevant endpoints:
|
|
305
|
+
|
|
306
|
+
- `GET /catalog/categories/tree`
|
|
307
|
+
- `GET /catalog/categories/:id/attributes`
|
|
308
|
+
- `GET /catalog/products/:id/attributes`
|
|
309
|
+
- `PUT /catalog/products/:id/attributes`
|
|
310
|
+
- `GET /catalog/products/:id/structured`
|
|
311
|
+
- `GET /catalog/products/:id/comparison-payload`
|
|
312
|
+
- `POST /catalog/products/:id/materialize-snapshots`
|
|
313
|
+
- generic CRUD under `/catalog/:resource`
|
|
314
|
+
|
|
315
|
+
## Safe transition strategy
|
|
316
|
+
|
|
317
|
+
The module preserves snapshot fields to avoid destructive migration behavior.
|
|
318
|
+
|
|
319
|
+
Current strategy:
|
|
320
|
+
|
|
321
|
+
- structured attribute tables are canonical
|
|
322
|
+
- snapshots remain readable
|
|
323
|
+
- snapshots are regenerated from structured values
|
|
324
|
+
- attribute updates trigger snapshot materialization
|
|
325
|
+
- compatibility aliases are still accepted in a few payloads where the old UI/model used different names
|
|
326
|
+
|
|
327
|
+
This keeps the module safe for gradual adoption while avoiding JSON-first modeling going forward.
|
|
328
|
+
|
|
329
|
+
## How to apply schema and seeds
|
|
330
|
+
|
|
331
|
+
This repository follows a database-first HedHog workflow.
|
|
332
|
+
|
|
333
|
+
### Schema/data apply
|
|
334
|
+
|
|
335
|
+
```powershell
|
|
336
|
+
hedhog dev apply
|
|
337
|
+
pnpm db:update
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
`hedhog dev apply` applies table YAML and the declarative data under `hedhog/data`, so the catalog seed data lives in the same standard workflow as the rest of the project.
|
|
341
|
+
|
|
342
|
+
### Build checks
|
|
343
|
+
|
|
344
|
+
```powershell
|
|
345
|
+
pnpm --filter api build
|
|
346
|
+
pnpm --filter @hed-hog/catalog build
|
|
347
|
+
pnpm --filter admin build
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
## Example flows
|
|
351
|
+
|
|
352
|
+
### 1. Create a brand with logo
|
|
353
|
+
|
|
354
|
+
1. Open `Catalog > Brands`
|
|
355
|
+
2. Create a brand with `name`, `slug`, and optional `website_url`
|
|
356
|
+
3. Upload the logo through the shared upload field
|
|
357
|
+
4. Save, which stores the uploaded file in `logo_file_id`
|
|
358
|
+
|
|
359
|
+
### 2. Create a category
|
|
360
|
+
|
|
361
|
+
1. Open `Catalog > Categories`
|
|
362
|
+
2. Create `gpu`-like or business-specific categories
|
|
363
|
+
3. Configure `comparison_enabled`, hierarchy, and status
|
|
364
|
+
|
|
365
|
+
### 3. Create an attribute
|
|
366
|
+
|
|
367
|
+
1. Open `Catalog > Attributes`
|
|
368
|
+
2. Define `code`, `slug`, `name`, `data_type`, and `group_name`
|
|
369
|
+
3. Mark whether the attribute is filterable/sortable/comparable
|
|
370
|
+
|
|
371
|
+
### 4. Link an attribute to a category
|
|
372
|
+
|
|
373
|
+
1. Open `Catalog > Category Attributes`
|
|
374
|
+
2. Pick the category and the attribute
|
|
375
|
+
3. Configure required/highlight/filter/comparison behavior
|
|
376
|
+
4. Save ordering and facet mode
|
|
377
|
+
|
|
378
|
+
### 5. Create a product
|
|
379
|
+
|
|
380
|
+
1. Open `Catalog > Products`
|
|
381
|
+
2. Fill the base product fields
|
|
382
|
+
3. Choose brand and category
|
|
383
|
+
4. Save the product
|
|
384
|
+
|
|
385
|
+
### 6. Save product technical attributes
|
|
386
|
+
|
|
387
|
+
1. In the same product sheet, after category selection, the technical fields load automatically
|
|
388
|
+
2. Fill typed values
|
|
389
|
+
3. Save the product
|
|
390
|
+
4. The admin persists values to `catalog_product_attribute_value`
|
|
391
|
+
5. Snapshots are materialized as derived JSON
|
|
392
|
+
|
|
393
|
+
## Starting with GPU and CPU
|
|
394
|
+
|
|
395
|
+
The seeded data is enough to bootstrap a real first catalog slice:
|
|
396
|
+
|
|
397
|
+
1. create brands like `NVIDIA`, `AMD`, `Intel`
|
|
398
|
+
2. create products under `gpu` and `cpu`
|
|
399
|
+
3. fill structured attributes
|
|
400
|
+
4. optionally attach product images
|
|
401
|
+
5. use comparison payload endpoints to power future storefront or editorial flows
|
|
402
|
+
|
|
403
|
+
## Future improvements
|
|
404
|
+
|
|
405
|
+
- automatic snapshot materialization on more product lifecycle events
|
|
406
|
+
- richer import pipelines that map external payloads directly to structured attributes
|
|
407
|
+
- category-specific admin presets and product templates
|
|
408
|
+
- storefront queries optimized for faceting and comparison at scale
|
|
409
|
+
- deeper read models for SEO, rankings, and recommendation features
|