@nextsparkjs/ai-workflow 0.1.0-beta.100
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/LICENSE +21 -0
- package/README.md +115 -0
- package/claude/_docs/workflows-optimizations.md +359 -0
- package/claude/agents/api-tester.md +634 -0
- package/claude/agents/architecture-supervisor.md +1351 -0
- package/claude/agents/backend-developer.md +997 -0
- package/claude/agents/backend-validator.md +417 -0
- package/claude/agents/bdd-docs-writer.md +737 -0
- package/claude/agents/block-developer.md +677 -0
- package/claude/agents/code-reviewer.md +1432 -0
- package/claude/agents/db-developer.md +721 -0
- package/claude/agents/db-validator.md +407 -0
- package/claude/agents/demo-video-generator.md +493 -0
- package/claude/agents/documentation-writer.md +1268 -0
- package/claude/agents/frontend-developer.md +1234 -0
- package/claude/agents/frontend-validator.md +777 -0
- package/claude/agents/functional-validator.md +630 -0
- package/claude/agents/mock-analyst.md +387 -0
- package/claude/agents/product-manager.md +963 -0
- package/claude/agents/qa-automation.md +1762 -0
- package/claude/agents/release-manager.md +634 -0
- package/claude/agents/selectors-translator.md +262 -0
- package/claude/agents/unit-test-writer.md +785 -0
- package/claude/agents/visual-comparator.md +329 -0
- package/claude/agents/workflow-maintainer.md +352 -0
- package/claude/commands/do/README.md +88 -0
- package/claude/commands/do/create-api.md +64 -0
- package/claude/commands/do/create-entity.md +66 -0
- package/claude/commands/do/create-migration.md +64 -0
- package/claude/commands/do/create-plugin.md +56 -0
- package/claude/commands/do/create-theme.md +70 -0
- package/claude/commands/do/mock-data.md +67 -0
- package/claude/commands/do/reset-db.md +71 -0
- package/claude/commands/do/setup-scheduled-action.md +75 -0
- package/claude/commands/do/sync-code-review.md +117 -0
- package/claude/commands/do/update-selectors.md +112 -0
- package/claude/commands/do/use-skills.md +90 -0
- package/claude/commands/do/validate-blocks.md +69 -0
- package/claude/commands/how-to/README.md +261 -0
- package/claude/commands/how-to/add-metadata.md +692 -0
- package/claude/commands/how-to/add-taxonomies.md +806 -0
- package/claude/commands/how-to/add-translations.md +571 -0
- package/claude/commands/how-to/create-api.md +577 -0
- package/claude/commands/how-to/create-block.md +575 -0
- package/claude/commands/how-to/create-child-entities.md +771 -0
- package/claude/commands/how-to/create-entity.md +597 -0
- package/claude/commands/how-to/create-migrations.md +605 -0
- package/claude/commands/how-to/create-plugin.md +654 -0
- package/claude/commands/how-to/customize-app.md +481 -0
- package/claude/commands/how-to/customize-dashboard.md +553 -0
- package/claude/commands/how-to/customize-theme.md +438 -0
- package/claude/commands/how-to/define-features-flows.md +632 -0
- package/claude/commands/how-to/deploy.md +507 -0
- package/claude/commands/how-to/handle-file-uploads.md +746 -0
- package/claude/commands/how-to/implement-search.md +1001 -0
- package/claude/commands/how-to/install-plugins.md +352 -0
- package/claude/commands/how-to/manage-test-coverage.md +984 -0
- package/claude/commands/how-to/run-tests.md +400 -0
- package/claude/commands/how-to/set-app-languages.md +601 -0
- package/claude/commands/how-to/set-plans-and-permissions.md +575 -0
- package/claude/commands/how-to/set-scheduled-actions.md +527 -0
- package/claude/commands/how-to/set-user-roles-and-permissions.md +550 -0
- package/claude/commands/how-to/setup-authentication.md +388 -0
- package/claude/commands/how-to/setup-claude-code.md +440 -0
- package/claude/commands/how-to/setup-database.md +274 -0
- package/claude/commands/how-to/setup-email-providers.md +598 -0
- package/claude/commands/how-to/setup-mobile-dev.md +627 -0
- package/claude/commands/how-to/start.md +500 -0
- package/claude/commands/how-to/use-devtools.md +639 -0
- package/claude/commands/how-to/use-superadmin.md +622 -0
- package/claude/commands/session/README.md +193 -0
- package/claude/commands/session/block-create.md +190 -0
- package/claude/commands/session/block-list.md +203 -0
- package/claude/commands/session/block-update.md +192 -0
- package/claude/commands/session/block-validate.md +218 -0
- package/claude/commands/session/changelog.md +115 -0
- package/claude/commands/session/close.md +225 -0
- package/claude/commands/session/commit.md +174 -0
- package/claude/commands/session/db-entity.md +206 -0
- package/claude/commands/session/db-fix.md +212 -0
- package/claude/commands/session/db-sample.md +206 -0
- package/claude/commands/session/demo.md +178 -0
- package/claude/commands/session/doc-bdd.md +207 -0
- package/claude/commands/session/doc-feature.md +218 -0
- package/claude/commands/session/doc-read.md +225 -0
- package/claude/commands/session/execute.md +204 -0
- package/claude/commands/session/explain.md +202 -0
- package/claude/commands/session/fix-bug.md +210 -0
- package/claude/commands/session/fix-build.md +182 -0
- package/claude/commands/session/fix-test.md +189 -0
- package/claude/commands/session/pending.md +232 -0
- package/claude/commands/session/refine.md +188 -0
- package/claude/commands/session/resume.md +192 -0
- package/claude/commands/session/review.md +192 -0
- package/claude/commands/session/scope-change.md +181 -0
- package/claude/commands/session/start-blocks.md +347 -0
- package/claude/commands/session/start.md +604 -0
- package/claude/commands/session/status.md +169 -0
- package/claude/commands/session/test-fix.md +221 -0
- package/claude/commands/session/test-run.md +203 -0
- package/claude/commands/session/test-write.md +242 -0
- package/claude/commands/session/validate.md +162 -0
- package/claude/config/context.json +40 -0
- package/claude/config/github.json +69 -0
- package/claude/config/github.schema.json +106 -0
- package/claude/config/team.json +46 -0
- package/claude/config/team.schema.json +106 -0
- package/claude/config/workspace.json +43 -0
- package/claude/config/workspace.schema.json +75 -0
- package/claude/skills/README.md +228 -0
- package/claude/skills/accessibility/SKILL.md +573 -0
- package/claude/skills/api-bypass-layers/SKILL.md +550 -0
- package/claude/skills/asana-integration/SKILL.md +499 -0
- package/claude/skills/better-auth/SKILL.md +666 -0
- package/claude/skills/billing-subscriptions/SKILL.md +660 -0
- package/claude/skills/block-decision-matrix/SKILL.md +359 -0
- package/claude/skills/clickup-integration/SKILL.md +434 -0
- package/claude/skills/core-theme-responsibilities/SKILL.md +485 -0
- package/claude/skills/create-plugin/SKILL.md +425 -0
- package/claude/skills/create-theme/SKILL.md +331 -0
- package/claude/skills/cypress-api/SKILL.md +511 -0
- package/claude/skills/cypress-api/scripts/generate-api-controller.py +329 -0
- package/claude/skills/cypress-api/scripts/generate-api-test.py +930 -0
- package/claude/skills/cypress-e2e/SKILL.md +526 -0
- package/claude/skills/cypress-e2e/scripts/extract-selectors.py +383 -0
- package/claude/skills/cypress-e2e/scripts/generate-uat-test.py +788 -0
- package/claude/skills/cypress-selectors/SKILL.md +309 -0
- package/claude/skills/cypress-selectors/scripts/extract-missing.py +243 -0
- package/claude/skills/cypress-selectors/scripts/generate-block-selectors.py +283 -0
- package/claude/skills/cypress-selectors/scripts/validate-selectors.py +145 -0
- package/claude/skills/database-migrations/SKILL.md +335 -0
- package/claude/skills/database-migrations/scripts/generate-sample-data.py +284 -0
- package/claude/skills/database-migrations/scripts/validate-migration.py +323 -0
- package/claude/skills/design-system/SKILL.md +682 -0
- package/claude/skills/documentation/SKILL.md +540 -0
- package/claude/skills/entity-api/SKILL.md +482 -0
- package/claude/skills/entity-system/SKILL.md +635 -0
- package/claude/skills/entity-system/scripts/generate-child-migration.py +298 -0
- package/claude/skills/entity-system/scripts/generate-metas-migration.py +233 -0
- package/claude/skills/entity-system/scripts/generate-migration.py +382 -0
- package/claude/skills/entity-system/scripts/generate-sample-data.py +418 -0
- package/claude/skills/entity-system/scripts/scaffold-entity.py +661 -0
- package/claude/skills/github/SKILL.md +467 -0
- package/claude/skills/i18n-nextintl/SKILL.md +302 -0
- package/claude/skills/i18n-nextintl/scripts/add-translation.py +243 -0
- package/claude/skills/i18n-nextintl/scripts/extract-hardcoded.py +246 -0
- package/claude/skills/i18n-nextintl/scripts/validate-translations.py +260 -0
- package/claude/skills/impact-analysis/SKILL.md +203 -0
- package/claude/skills/jest-unit/SKILL.md +306 -0
- package/claude/skills/jest-unit/references/component-testing.md +371 -0
- package/claude/skills/jest-unit/references/mocking-patterns.md +380 -0
- package/claude/skills/jest-unit/references/service-hook-testing.md +454 -0
- package/claude/skills/jira-integration/SKILL.md +539 -0
- package/claude/skills/media-library/SKILL.md +743 -0
- package/claude/skills/mock-analysis/SKILL.md +276 -0
- package/claude/skills/monorepo-architecture/SKILL.md +162 -0
- package/claude/skills/nextjs-api-development/SKILL.md +364 -0
- package/claude/skills/nextjs-api-development/scripts/generate-crud-tests.py +456 -0
- package/claude/skills/nextjs-api-development/scripts/scaffold-endpoint.py +481 -0
- package/claude/skills/nextjs-api-development/scripts/validate-api.py +283 -0
- package/claude/skills/notion-integration/SKILL.md +641 -0
- package/claude/skills/npm-development-workflow/SKILL.md +480 -0
- package/claude/skills/page-builder-blocks/SKILL.md +530 -0
- package/claude/skills/page-builder-blocks/scripts/scaffold-block.py +444 -0
- package/claude/skills/permissions-system/SKILL.md +619 -0
- package/claude/skills/plugins/SKILL.md +340 -0
- package/claude/skills/plugins/references/plugin-templates.md +414 -0
- package/claude/skills/plugins/references/plugin-testing.md +353 -0
- package/claude/skills/plugins/references/plugin-types.md +198 -0
- package/claude/skills/plugins/scripts/scaffold-plugin.py +443 -0
- package/claude/skills/pom-patterns/SKILL.md +452 -0
- package/claude/skills/pom-patterns/scripts/generate-pom.py +392 -0
- package/claude/skills/rate-limiting/SKILL.md +342 -0
- package/claude/skills/react-best-practices/AGENTS.md +2410 -0
- package/claude/skills/react-best-practices/README.md +123 -0
- package/claude/skills/react-best-practices/SKILL.md +125 -0
- package/claude/skills/react-best-practices/metadata.json +15 -0
- package/claude/skills/react-best-practices/rules/_sections.md +46 -0
- package/claude/skills/react-best-practices/rules/_template.md +28 -0
- package/claude/skills/react-best-practices/rules/advanced-event-handler-refs.md +55 -0
- package/claude/skills/react-best-practices/rules/advanced-use-latest.md +49 -0
- package/claude/skills/react-best-practices/rules/async-api-routes.md +38 -0
- package/claude/skills/react-best-practices/rules/async-defer-await.md +80 -0
- package/claude/skills/react-best-practices/rules/async-dependencies.md +36 -0
- package/claude/skills/react-best-practices/rules/async-parallel.md +28 -0
- package/claude/skills/react-best-practices/rules/async-suspense-boundaries.md +99 -0
- package/claude/skills/react-best-practices/rules/bundle-barrel-imports.md +59 -0
- package/claude/skills/react-best-practices/rules/bundle-conditional.md +31 -0
- package/claude/skills/react-best-practices/rules/bundle-defer-third-party.md +49 -0
- package/claude/skills/react-best-practices/rules/bundle-dynamic-imports.md +35 -0
- package/claude/skills/react-best-practices/rules/bundle-preload.md +50 -0
- package/claude/skills/react-best-practices/rules/client-event-listeners.md +74 -0
- package/claude/skills/react-best-practices/rules/client-localstorage-schema.md +71 -0
- package/claude/skills/react-best-practices/rules/client-passive-event-listeners.md +48 -0
- package/claude/skills/react-best-practices/rules/client-swr-dedup.md +56 -0
- package/claude/skills/react-best-practices/rules/js-batch-dom-css.md +82 -0
- package/claude/skills/react-best-practices/rules/js-cache-function-results.md +80 -0
- package/claude/skills/react-best-practices/rules/js-cache-property-access.md +28 -0
- package/claude/skills/react-best-practices/rules/js-cache-storage.md +70 -0
- package/claude/skills/react-best-practices/rules/js-combine-iterations.md +32 -0
- package/claude/skills/react-best-practices/rules/js-early-exit.md +50 -0
- package/claude/skills/react-best-practices/rules/js-hoist-regexp.md +45 -0
- package/claude/skills/react-best-practices/rules/js-index-maps.md +37 -0
- package/claude/skills/react-best-practices/rules/js-length-check-first.md +49 -0
- package/claude/skills/react-best-practices/rules/js-min-max-loop.md +82 -0
- package/claude/skills/react-best-practices/rules/js-set-map-lookups.md +24 -0
- package/claude/skills/react-best-practices/rules/js-tosorted-immutable.md +57 -0
- package/claude/skills/react-best-practices/rules/rendering-activity.md +26 -0
- package/claude/skills/react-best-practices/rules/rendering-animate-svg-wrapper.md +47 -0
- package/claude/skills/react-best-practices/rules/rendering-conditional-render.md +40 -0
- package/claude/skills/react-best-practices/rules/rendering-content-visibility.md +38 -0
- package/claude/skills/react-best-practices/rules/rendering-hoist-jsx.md +46 -0
- package/claude/skills/react-best-practices/rules/rendering-hydration-no-flicker.md +82 -0
- package/claude/skills/react-best-practices/rules/rendering-svg-precision.md +28 -0
- package/claude/skills/react-best-practices/rules/rerender-defer-reads.md +39 -0
- package/claude/skills/react-best-practices/rules/rerender-dependencies.md +45 -0
- package/claude/skills/react-best-practices/rules/rerender-derived-state.md +29 -0
- package/claude/skills/react-best-practices/rules/rerender-functional-setstate.md +74 -0
- package/claude/skills/react-best-practices/rules/rerender-lazy-state-init.md +58 -0
- package/claude/skills/react-best-practices/rules/rerender-memo.md +44 -0
- package/claude/skills/react-best-practices/rules/rerender-transitions.md +40 -0
- package/claude/skills/react-best-practices/rules/server-after-nonblocking.md +73 -0
- package/claude/skills/react-best-practices/rules/server-cache-lru.md +41 -0
- package/claude/skills/react-best-practices/rules/server-cache-react.md +76 -0
- package/claude/skills/react-best-practices/rules/server-parallel-fetching.md +83 -0
- package/claude/skills/react-best-practices/rules/server-serialization.md +38 -0
- package/claude/skills/react-patterns/SKILL.md +688 -0
- package/claude/skills/registry-system/SKILL.md +331 -0
- package/claude/skills/scheduled-actions/SKILL.md +671 -0
- package/claude/skills/scope-enforcement/SKILL.md +542 -0
- package/claude/skills/scope-enforcement/scripts/validate-scope.py +357 -0
- package/claude/skills/server-actions/SKILL.md +493 -0
- package/claude/skills/service-layer/SKILL.md +587 -0
- package/claude/skills/session-management/SKILL.md +266 -0
- package/claude/skills/session-management/scripts/create-session.py +166 -0
- package/claude/skills/session-management/scripts/iteration-close.sh +105 -0
- package/claude/skills/session-management/scripts/iteration-init.sh +180 -0
- package/claude/skills/session-management/scripts/session-archive.sh +87 -0
- package/claude/skills/session-management/scripts/session-close.sh +133 -0
- package/claude/skills/session-management/scripts/session-init.sh +225 -0
- package/claude/skills/session-management/scripts/session-list.sh +163 -0
- package/claude/skills/session-management/scripts/split-plan.sh +116 -0
- package/claude/skills/shadcn-components/SKILL.md +586 -0
- package/claude/skills/shadcn-theming/SKILL.md +446 -0
- package/claude/skills/suspense-loading/SKILL.md +280 -0
- package/claude/skills/tailwind-theming/SKILL.md +507 -0
- package/claude/skills/tanstack-query/SKILL.md +608 -0
- package/claude/skills/test-coverage/SKILL.md +239 -0
- package/claude/skills/web-design-guidelines/SKILL.md +39 -0
- package/claude/skills/zod-validation/SKILL.md +537 -0
- package/claude/templates/blocks/progress.md +86 -0
- package/claude/templates/iteration/changes.md +61 -0
- package/claude/templates/iteration/progress.md +55 -0
- package/claude/templates/log.md +31 -0
- package/claude/templates/story/context.md +77 -0
- package/claude/templates/story/pendings.md +37 -0
- package/claude/templates/story/plan.md +299 -0
- package/claude/templates/story/requirements.md +109 -0
- package/claude/templates/story/scope.json +10 -0
- package/claude/templates/story/tests.md +91 -0
- package/claude/templates/task/progress.md +58 -0
- package/claude/templates/task/requirements.md +54 -0
- package/claude/workflows/README.md +154 -0
- package/claude/workflows/blocks.md +614 -0
- package/claude/workflows/story.md +1207 -0
- package/claude/workflows/task.md +927 -0
- package/claude/workflows/tweak.md +527 -0
- package/cursor/.gitkeep +0 -0
- package/package.json +35 -0
- package/scripts/postinstall.mjs +198 -0
- package/scripts/setup.mjs +282 -0
- package/scripts/sync.mjs +209 -0
|
@@ -0,0 +1,806 @@
|
|
|
1
|
+
# /how-to:add-taxonomies
|
|
2
|
+
|
|
3
|
+
Interactive guide to implementing taxonomies (tags and categories) in NextSpark.
|
|
4
|
+
|
|
5
|
+
**Aliases:** `/how-to:tags`, `/how-to:categories`
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Required Skills
|
|
10
|
+
|
|
11
|
+
Before executing, these skills provide deeper context:
|
|
12
|
+
- `.claude/skills/database-migrations/SKILL.md` - Creating taxonomy tables
|
|
13
|
+
- `.claude/skills/entity-system/SKILL.md` - Entity field definitions
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Syntax
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
/how-to:add-taxonomies
|
|
21
|
+
/how-to:add-taxonomies --type tags
|
|
22
|
+
/how-to:add-taxonomies --entity posts
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Behavior
|
|
28
|
+
|
|
29
|
+
Guides the user through implementing a taxonomy system with tags, categories, and hierarchical classifications.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Tutorial Structure
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
STEPS OVERVIEW (5 steps)
|
|
37
|
+
|
|
38
|
+
Step 1: Understanding Taxonomies
|
|
39
|
+
└── Tags, categories, hierarchy
|
|
40
|
+
|
|
41
|
+
Step 2: Taxonomy Table Structure
|
|
42
|
+
└── Fields, constraints, indexes
|
|
43
|
+
|
|
44
|
+
Step 3: Creating Taxonomy Types
|
|
45
|
+
└── post_category, product_tag, etc.
|
|
46
|
+
|
|
47
|
+
Step 4: Linking Entities to Taxonomies
|
|
48
|
+
└── Join tables, many-to-many
|
|
49
|
+
|
|
50
|
+
Step 5: UI Components
|
|
51
|
+
└── Tag selectors, category dropdowns
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Step 1: Understanding Taxonomies
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
60
|
+
📚 HOW TO: ADD TAXONOMIES
|
|
61
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
62
|
+
|
|
63
|
+
STEP 1 OF 5: Understanding Taxonomies
|
|
64
|
+
|
|
65
|
+
Taxonomies provide a flexible classification
|
|
66
|
+
system for organizing content.
|
|
67
|
+
|
|
68
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**📋 What are Taxonomies?**
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
┌─────────────────────────────────────────────┐
|
|
75
|
+
│ TAXONOMY = Classification System │
|
|
76
|
+
│ ───────────────────────────────────────── │
|
|
77
|
+
│ │
|
|
78
|
+
│ TAGS (flat): │
|
|
79
|
+
│ [javascript] [react] [nextjs] [tutorial] │
|
|
80
|
+
│ │
|
|
81
|
+
│ CATEGORIES (hierarchical): │
|
|
82
|
+
│ Technology │
|
|
83
|
+
│ ├── Frontend │
|
|
84
|
+
│ │ ├── React │
|
|
85
|
+
│ │ └── Vue │
|
|
86
|
+
│ └── Backend │
|
|
87
|
+
│ ├── Node.js │
|
|
88
|
+
│ └── Python │
|
|
89
|
+
└─────────────────────────────────────────────┘
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
**📋 Tags vs Categories:**
|
|
93
|
+
|
|
94
|
+
| Feature | Tags | Categories |
|
|
95
|
+
|---------|------|------------|
|
|
96
|
+
| Structure | Flat | Hierarchical |
|
|
97
|
+
| Multiple per item | Yes (many) | Usually 1-2 |
|
|
98
|
+
| User created | Often | Usually admin |
|
|
99
|
+
| Purpose | Discovery | Organization |
|
|
100
|
+
| Example | #react, #tutorial | Technology > Frontend |
|
|
101
|
+
|
|
102
|
+
**📋 Common Taxonomy Types:**
|
|
103
|
+
|
|
104
|
+
| Type | Used For | Example |
|
|
105
|
+
|------|----------|---------|
|
|
106
|
+
| `post_category` | Blog posts | Technology, Lifestyle |
|
|
107
|
+
| `post_tag` | Blog tags | #react, #nextjs |
|
|
108
|
+
| `product_category` | E-commerce | Electronics, Clothing |
|
|
109
|
+
| `product_tag` | Product tags | #sale, #new |
|
|
110
|
+
| `portfolio_type` | Portfolio items | Web, Mobile, Design |
|
|
111
|
+
| `skill` | User skills | JavaScript, Design |
|
|
112
|
+
|
|
113
|
+
**📋 Features:**
|
|
114
|
+
|
|
115
|
+
```
|
|
116
|
+
✓ Hierarchical support (parent-child)
|
|
117
|
+
✓ Visual customization (icon, color)
|
|
118
|
+
✓ Slug-based URLs (/blog/category/technology)
|
|
119
|
+
✓ Ordering support
|
|
120
|
+
✓ Soft delete
|
|
121
|
+
✓ Metadata storage (JSONB)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
126
|
+
|
|
127
|
+
What would you like to do?
|
|
128
|
+
|
|
129
|
+
[1] Continue to Step 2 (Table Structure)
|
|
130
|
+
[2] What's the difference from enum fields?
|
|
131
|
+
[3] Can users create their own tags?
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## Step 2: Taxonomy Table Structure
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
140
|
+
STEP 2 OF 5: Taxonomy Table Structure
|
|
141
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
142
|
+
|
|
143
|
+
The taxonomies table provides a flexible
|
|
144
|
+
structure for all classification types.
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
**📋 Taxonomies Table Schema:**
|
|
148
|
+
|
|
149
|
+
```sql
|
|
150
|
+
-- migrations/006_taxonomies_table.sql
|
|
151
|
+
|
|
152
|
+
CREATE TABLE taxonomies (
|
|
153
|
+
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
|
154
|
+
userId TEXT REFERENCES users(id),
|
|
155
|
+
teamId TEXT REFERENCES teams(id),
|
|
156
|
+
|
|
157
|
+
-- Classification
|
|
158
|
+
type TEXT NOT NULL, -- 'post_category', 'tag', etc.
|
|
159
|
+
slug TEXT NOT NULL, -- URL-friendly: 'my-category'
|
|
160
|
+
name TEXT NOT NULL, -- Display name: 'My Category'
|
|
161
|
+
description TEXT, -- Optional description
|
|
162
|
+
|
|
163
|
+
-- Visual customization
|
|
164
|
+
icon TEXT, -- Lucide icon name
|
|
165
|
+
color TEXT, -- Hex color or name
|
|
166
|
+
|
|
167
|
+
-- Hierarchy support
|
|
168
|
+
parentId TEXT REFERENCES taxonomies(id),
|
|
169
|
+
|
|
170
|
+
-- Metadata & ordering
|
|
171
|
+
metadata JSONB DEFAULT '{}', -- Flexible extra data
|
|
172
|
+
"order" INTEGER DEFAULT 0, -- Manual ordering
|
|
173
|
+
|
|
174
|
+
-- Status
|
|
175
|
+
isDefault BOOLEAN DEFAULT FALSE, -- Default selection
|
|
176
|
+
isActive BOOLEAN DEFAULT TRUE, -- Enable/disable
|
|
177
|
+
|
|
178
|
+
-- Timestamps
|
|
179
|
+
createdAt TIMESTAMPTZ DEFAULT now(),
|
|
180
|
+
updatedAt TIMESTAMPTZ DEFAULT now(),
|
|
181
|
+
deletedAt TIMESTAMPTZ, -- Soft delete
|
|
182
|
+
|
|
183
|
+
-- Constraints
|
|
184
|
+
UNIQUE(type, slug, teamId),
|
|
185
|
+
CHECK(slug ~ '^[a-z0-9\-]+$'),
|
|
186
|
+
CHECK(LENGTH(slug) >= 2 AND LENGTH(slug) <= 100),
|
|
187
|
+
CHECK(LENGTH(TRIM(name)) > 0)
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
-- Enable RLS
|
|
191
|
+
ALTER TABLE taxonomies ENABLE ROW LEVEL SECURITY;
|
|
192
|
+
|
|
193
|
+
-- Indexes
|
|
194
|
+
CREATE INDEX idx_taxonomies_type ON taxonomies(type);
|
|
195
|
+
CREATE INDEX idx_taxonomies_slug ON taxonomies(slug);
|
|
196
|
+
CREATE INDEX idx_taxonomies_team_id ON taxonomies(teamId);
|
|
197
|
+
CREATE INDEX idx_taxonomies_parent_id ON taxonomies(parentId);
|
|
198
|
+
CREATE INDEX idx_taxonomies_active ON taxonomies(isActive)
|
|
199
|
+
WHERE isActive = TRUE;
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
**📋 Key Fields Explained:**
|
|
203
|
+
|
|
204
|
+
| Field | Purpose | Example |
|
|
205
|
+
|-------|---------|---------|
|
|
206
|
+
| `type` | Taxonomy classification | `'post_category'` |
|
|
207
|
+
| `slug` | URL-safe identifier | `'web-development'` |
|
|
208
|
+
| `name` | Display name | `'Web Development'` |
|
|
209
|
+
| `parentId` | Hierarchy support | Links to parent taxonomy |
|
|
210
|
+
| `icon` | Visual icon | `'Code'` (Lucide) |
|
|
211
|
+
| `color` | Visual color | `'#3B82F6'` |
|
|
212
|
+
| `metadata` | Flexible storage | `{ "featured": true }` |
|
|
213
|
+
| `order` | Manual sorting | `1, 2, 3...` |
|
|
214
|
+
|
|
215
|
+
**📋 Slug Validation:**
|
|
216
|
+
|
|
217
|
+
```sql
|
|
218
|
+
-- Only lowercase letters, numbers, and hyphens
|
|
219
|
+
CHECK(slug ~ '^[a-z0-9\-]+$')
|
|
220
|
+
|
|
221
|
+
-- Minimum 2, maximum 100 characters
|
|
222
|
+
CHECK(LENGTH(slug) >= 2 AND LENGTH(slug) <= 100)
|
|
223
|
+
|
|
224
|
+
-- Examples:
|
|
225
|
+
-- ✓ 'web-development'
|
|
226
|
+
-- ✓ 'react-18'
|
|
227
|
+
-- ✗ 'Web Development' (uppercase)
|
|
228
|
+
-- ✗ 'web_development' (underscore)
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
```
|
|
232
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
233
|
+
|
|
234
|
+
What would you like to do?
|
|
235
|
+
|
|
236
|
+
[1] Continue to Step 3 (Creating Taxonomy Types)
|
|
237
|
+
[2] How do I add RLS policies?
|
|
238
|
+
[3] Can taxonomies be global (no team)?
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## Step 3: Creating Taxonomy Types
|
|
244
|
+
|
|
245
|
+
```
|
|
246
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
247
|
+
STEP 3 OF 5: Creating Taxonomy Types
|
|
248
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
249
|
+
|
|
250
|
+
Define taxonomy types for different content
|
|
251
|
+
classifications in your app.
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
**📋 Sample Data Migration:**
|
|
255
|
+
|
|
256
|
+
```sql
|
|
257
|
+
-- migrations/007_taxonomies_sample_data.sql
|
|
258
|
+
|
|
259
|
+
-- Post categories (hierarchical)
|
|
260
|
+
INSERT INTO taxonomies (type, slug, name, icon, color, "order", teamId)
|
|
261
|
+
SELECT
|
|
262
|
+
'post_category',
|
|
263
|
+
slug,
|
|
264
|
+
name,
|
|
265
|
+
icon,
|
|
266
|
+
color,
|
|
267
|
+
row_number() OVER (),
|
|
268
|
+
(SELECT id FROM teams LIMIT 1)
|
|
269
|
+
FROM (VALUES
|
|
270
|
+
('technology', 'Technology', 'Cpu', '#3B82F6'),
|
|
271
|
+
('lifestyle', 'Lifestyle', 'Heart', '#EC4899'),
|
|
272
|
+
('business', 'Business', 'Briefcase', '#10B981'),
|
|
273
|
+
('tutorials', 'Tutorials', 'GraduationCap', '#F59E0B')
|
|
274
|
+
) AS t(slug, name, icon, color);
|
|
275
|
+
|
|
276
|
+
-- Sub-categories (with parent)
|
|
277
|
+
INSERT INTO taxonomies (type, slug, name, parentId, teamId)
|
|
278
|
+
SELECT
|
|
279
|
+
'post_category',
|
|
280
|
+
slug,
|
|
281
|
+
name,
|
|
282
|
+
(SELECT id FROM taxonomies WHERE slug = parent_slug AND type = 'post_category'),
|
|
283
|
+
(SELECT id FROM teams LIMIT 1)
|
|
284
|
+
FROM (VALUES
|
|
285
|
+
('frontend', 'Frontend', 'technology'),
|
|
286
|
+
('backend', 'Backend', 'technology'),
|
|
287
|
+
('devops', 'DevOps', 'technology'),
|
|
288
|
+
('health', 'Health', 'lifestyle'),
|
|
289
|
+
('travel', 'Travel', 'lifestyle')
|
|
290
|
+
) AS t(slug, name, parent_slug);
|
|
291
|
+
|
|
292
|
+
-- Post tags (flat)
|
|
293
|
+
INSERT INTO taxonomies (type, slug, name, color, teamId)
|
|
294
|
+
SELECT
|
|
295
|
+
'post_tag',
|
|
296
|
+
slug,
|
|
297
|
+
name,
|
|
298
|
+
color,
|
|
299
|
+
(SELECT id FROM teams LIMIT 1)
|
|
300
|
+
FROM (VALUES
|
|
301
|
+
('javascript', 'JavaScript', '#F7DF1E'),
|
|
302
|
+
('react', 'React', '#61DAFB'),
|
|
303
|
+
('nextjs', 'Next.js', '#000000'),
|
|
304
|
+
('typescript', 'TypeScript', '#3178C6'),
|
|
305
|
+
('tutorial', 'Tutorial', '#10B981'),
|
|
306
|
+
('beginner', 'Beginner', '#8B5CF6')
|
|
307
|
+
) AS t(slug, name, color);
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
**📋 Querying Taxonomies:**
|
|
311
|
+
|
|
312
|
+
```typescript
|
|
313
|
+
// Get all categories with children
|
|
314
|
+
const categories = await db.query(`
|
|
315
|
+
SELECT
|
|
316
|
+
t.*,
|
|
317
|
+
(
|
|
318
|
+
SELECT json_agg(c.*)
|
|
319
|
+
FROM taxonomies c
|
|
320
|
+
WHERE c.parentId = t.id
|
|
321
|
+
ORDER BY c."order"
|
|
322
|
+
) as children
|
|
323
|
+
FROM taxonomies t
|
|
324
|
+
WHERE t.type = 'post_category'
|
|
325
|
+
AND t.parentId IS NULL
|
|
326
|
+
AND t.isActive = true
|
|
327
|
+
AND t.teamId = $1
|
|
328
|
+
ORDER BY t."order"
|
|
329
|
+
`, [teamId])
|
|
330
|
+
|
|
331
|
+
// Result:
|
|
332
|
+
// [
|
|
333
|
+
// {
|
|
334
|
+
// id: '...',
|
|
335
|
+
// name: 'Technology',
|
|
336
|
+
// slug: 'technology',
|
|
337
|
+
// children: [
|
|
338
|
+
// { name: 'Frontend', slug: 'frontend' },
|
|
339
|
+
// { name: 'Backend', slug: 'backend' }
|
|
340
|
+
// ]
|
|
341
|
+
// },
|
|
342
|
+
// ...
|
|
343
|
+
// ]
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
**📋 Taxonomy Service:**
|
|
347
|
+
|
|
348
|
+
```typescript
|
|
349
|
+
// lib/services/taxonomy.service.ts
|
|
350
|
+
export class TaxonomyService {
|
|
351
|
+
static async getByType(type: string, teamId: string) {
|
|
352
|
+
return db.query(
|
|
353
|
+
'SELECT * FROM taxonomies WHERE type = $1 AND teamId = $2 AND isActive = true ORDER BY "order"',
|
|
354
|
+
[type, teamId]
|
|
355
|
+
)
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
static async getHierarchy(type: string, teamId: string) {
|
|
359
|
+
const all = await this.getByType(type, teamId)
|
|
360
|
+
return buildTree(all) // Helper to build nested structure
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
static async create(data: CreateTaxonomyInput) {
|
|
364
|
+
const slug = slugify(data.name)
|
|
365
|
+
return db.query(
|
|
366
|
+
'INSERT INTO taxonomies (type, slug, name, teamId, ...) VALUES ($1, $2, $3, $4, ...) RETURNING *',
|
|
367
|
+
[data.type, slug, data.name, data.teamId, ...]
|
|
368
|
+
)
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
```
|
|
374
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
375
|
+
|
|
376
|
+
What would you like to do?
|
|
377
|
+
|
|
378
|
+
[1] Continue to Step 4 (Linking Entities)
|
|
379
|
+
[2] How do I auto-generate slugs?
|
|
380
|
+
[3] Can I import taxonomies from a file?
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
---
|
|
384
|
+
|
|
385
|
+
## Step 4: Linking Entities to Taxonomies
|
|
386
|
+
|
|
387
|
+
```
|
|
388
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
389
|
+
STEP 4 OF 5: Linking Entities to Taxonomies
|
|
390
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
391
|
+
|
|
392
|
+
Connect entities to taxonomies using
|
|
393
|
+
join tables for many-to-many relationships.
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
**📋 Entity-Taxonomy Relations Table:**
|
|
397
|
+
|
|
398
|
+
```sql
|
|
399
|
+
-- migrations/008_entity_taxonomy_relations.sql
|
|
400
|
+
|
|
401
|
+
CREATE TABLE entity_taxonomy_relations (
|
|
402
|
+
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
|
403
|
+
|
|
404
|
+
-- Entity reference (polymorphic)
|
|
405
|
+
entityType TEXT NOT NULL, -- 'posts', 'products', etc.
|
|
406
|
+
entityId TEXT NOT NULL, -- ID of the entity
|
|
407
|
+
|
|
408
|
+
-- Taxonomy reference
|
|
409
|
+
taxonomyId TEXT NOT NULL REFERENCES taxonomies(id) ON DELETE CASCADE,
|
|
410
|
+
|
|
411
|
+
-- Ordering within entity
|
|
412
|
+
"order" INTEGER DEFAULT 0,
|
|
413
|
+
|
|
414
|
+
-- Timestamps
|
|
415
|
+
createdAt TIMESTAMPTZ DEFAULT now(),
|
|
416
|
+
|
|
417
|
+
-- Constraints
|
|
418
|
+
UNIQUE(entityType, entityId, taxonomyId)
|
|
419
|
+
);
|
|
420
|
+
|
|
421
|
+
-- Indexes
|
|
422
|
+
CREATE INDEX idx_etr_entity ON entity_taxonomy_relations(entityType, entityId);
|
|
423
|
+
CREATE INDEX idx_etr_taxonomy ON entity_taxonomy_relations(taxonomyId);
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
**📋 Alternative: Direct Foreign Key:**
|
|
427
|
+
|
|
428
|
+
```sql
|
|
429
|
+
-- For single category per entity
|
|
430
|
+
ALTER TABLE posts ADD COLUMN categoryId TEXT REFERENCES taxonomies(id);
|
|
431
|
+
|
|
432
|
+
-- For multiple tags (JSONB array)
|
|
433
|
+
ALTER TABLE posts ADD COLUMN tagIds TEXT[] DEFAULT '{}';
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
**📋 Linking Examples:**
|
|
437
|
+
|
|
438
|
+
```typescript
|
|
439
|
+
// Add category to post
|
|
440
|
+
await db.query(`
|
|
441
|
+
INSERT INTO entity_taxonomy_relations (entityType, entityId, taxonomyId)
|
|
442
|
+
VALUES ('posts', $1, $2)
|
|
443
|
+
`, [postId, categoryId])
|
|
444
|
+
|
|
445
|
+
// Add multiple tags
|
|
446
|
+
const tagIds = ['tag-1', 'tag-2', 'tag-3']
|
|
447
|
+
for (const tagId of tagIds) {
|
|
448
|
+
await db.query(`
|
|
449
|
+
INSERT INTO entity_taxonomy_relations (entityType, entityId, taxonomyId)
|
|
450
|
+
VALUES ('posts', $1, $2)
|
|
451
|
+
ON CONFLICT (entityType, entityId, taxonomyId) DO NOTHING
|
|
452
|
+
`, [postId, tagId])
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// Remove tag from post
|
|
456
|
+
await db.query(`
|
|
457
|
+
DELETE FROM entity_taxonomy_relations
|
|
458
|
+
WHERE entityType = 'posts' AND entityId = $1 AND taxonomyId = $2
|
|
459
|
+
`, [postId, tagId])
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
**📋 Query with Taxonomies:**
|
|
463
|
+
|
|
464
|
+
```typescript
|
|
465
|
+
// Get posts with their categories and tags
|
|
466
|
+
const posts = await db.query(`
|
|
467
|
+
SELECT
|
|
468
|
+
p.*,
|
|
469
|
+
(
|
|
470
|
+
SELECT json_agg(t.*)
|
|
471
|
+
FROM entity_taxonomy_relations etr
|
|
472
|
+
JOIN taxonomies t ON t.id = etr.taxonomyId
|
|
473
|
+
WHERE etr.entityType = 'posts'
|
|
474
|
+
AND etr.entityId = p.id
|
|
475
|
+
AND t.type = 'post_category'
|
|
476
|
+
) as categories,
|
|
477
|
+
(
|
|
478
|
+
SELECT json_agg(t.*)
|
|
479
|
+
FROM entity_taxonomy_relations etr
|
|
480
|
+
JOIN taxonomies t ON t.id = etr.taxonomyId
|
|
481
|
+
WHERE etr.entityType = 'posts'
|
|
482
|
+
AND etr.entityId = p.id
|
|
483
|
+
AND t.type = 'post_tag'
|
|
484
|
+
) as tags
|
|
485
|
+
FROM posts p
|
|
486
|
+
WHERE p.teamId = $1
|
|
487
|
+
`, [teamId])
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
**📋 Filter by Taxonomy:**
|
|
491
|
+
|
|
492
|
+
```typescript
|
|
493
|
+
// Get posts in a specific category
|
|
494
|
+
const posts = await db.query(`
|
|
495
|
+
SELECT p.*
|
|
496
|
+
FROM posts p
|
|
497
|
+
WHERE p.id IN (
|
|
498
|
+
SELECT etr.entityId
|
|
499
|
+
FROM entity_taxonomy_relations etr
|
|
500
|
+
JOIN taxonomies t ON t.id = etr.taxonomyId
|
|
501
|
+
WHERE etr.entityType = 'posts'
|
|
502
|
+
AND t.slug = $1
|
|
503
|
+
AND t.type = 'post_category'
|
|
504
|
+
)
|
|
505
|
+
`, [categorySlug])
|
|
506
|
+
|
|
507
|
+
// Get posts with specific tag
|
|
508
|
+
// GET /api/v1/posts?tag=react
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
```
|
|
512
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
513
|
+
|
|
514
|
+
What would you like to do?
|
|
515
|
+
|
|
516
|
+
[1] Continue to Step 5 (UI Components)
|
|
517
|
+
[2] How do I handle taxonomy counts?
|
|
518
|
+
[3] Can I have required taxonomies?
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
---
|
|
522
|
+
|
|
523
|
+
## Step 5: UI Components
|
|
524
|
+
|
|
525
|
+
```
|
|
526
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
527
|
+
STEP 5 OF 5: UI Components
|
|
528
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
529
|
+
|
|
530
|
+
Create UI components for selecting and
|
|
531
|
+
displaying taxonomies.
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
**📋 Category Select Component:**
|
|
535
|
+
|
|
536
|
+
```typescript
|
|
537
|
+
// components/CategorySelect.tsx
|
|
538
|
+
'use client'
|
|
539
|
+
|
|
540
|
+
import { useQuery } from '@tanstack/react-query'
|
|
541
|
+
import {
|
|
542
|
+
Select,
|
|
543
|
+
SelectContent,
|
|
544
|
+
SelectItem,
|
|
545
|
+
SelectTrigger,
|
|
546
|
+
SelectValue,
|
|
547
|
+
} from '@/core/components/ui/select'
|
|
548
|
+
|
|
549
|
+
interface Props {
|
|
550
|
+
value: string
|
|
551
|
+
onChange: (value: string) => void
|
|
552
|
+
type: string
|
|
553
|
+
teamId: string
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
export function CategorySelect({ value, onChange, type, teamId }: Props) {
|
|
557
|
+
const { data: categories } = useQuery({
|
|
558
|
+
queryKey: ['taxonomies', type, teamId],
|
|
559
|
+
queryFn: () => fetch(`/api/v1/taxonomies?type=${type}`).then(r => r.json())
|
|
560
|
+
})
|
|
561
|
+
|
|
562
|
+
return (
|
|
563
|
+
<Select value={value} onValueChange={onChange}>
|
|
564
|
+
<SelectTrigger>
|
|
565
|
+
<SelectValue placeholder="Select category" />
|
|
566
|
+
</SelectTrigger>
|
|
567
|
+
<SelectContent>
|
|
568
|
+
{categories?.data?.map((cat) => (
|
|
569
|
+
<SelectItem key={cat.id} value={cat.id}>
|
|
570
|
+
{cat.icon && <span className="mr-2">{cat.icon}</span>}
|
|
571
|
+
{cat.name}
|
|
572
|
+
</SelectItem>
|
|
573
|
+
))}
|
|
574
|
+
</SelectContent>
|
|
575
|
+
</Select>
|
|
576
|
+
)
|
|
577
|
+
}
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
**📋 Tag Multi-Select Component:**
|
|
581
|
+
|
|
582
|
+
```typescript
|
|
583
|
+
// components/TagSelect.tsx
|
|
584
|
+
'use client'
|
|
585
|
+
|
|
586
|
+
import { useState } from 'react'
|
|
587
|
+
import { useQuery } from '@tanstack/react-query'
|
|
588
|
+
import { Badge } from '@/core/components/ui/badge'
|
|
589
|
+
import { X } from 'lucide-react'
|
|
590
|
+
|
|
591
|
+
interface Props {
|
|
592
|
+
value: string[]
|
|
593
|
+
onChange: (value: string[]) => void
|
|
594
|
+
type: string
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
export function TagSelect({ value, onChange, type }: Props) {
|
|
598
|
+
const [search, setSearch] = useState('')
|
|
599
|
+
|
|
600
|
+
const { data: tags } = useQuery({
|
|
601
|
+
queryKey: ['taxonomies', type],
|
|
602
|
+
queryFn: () => fetch(`/api/v1/taxonomies?type=${type}`).then(r => r.json())
|
|
603
|
+
})
|
|
604
|
+
|
|
605
|
+
const selectedTags = tags?.data?.filter(t => value.includes(t.id)) || []
|
|
606
|
+
const availableTags = tags?.data?.filter(t =>
|
|
607
|
+
!value.includes(t.id) &&
|
|
608
|
+
t.name.toLowerCase().includes(search.toLowerCase())
|
|
609
|
+
) || []
|
|
610
|
+
|
|
611
|
+
const addTag = (tagId: string) => {
|
|
612
|
+
onChange([...value, tagId])
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
const removeTag = (tagId: string) => {
|
|
616
|
+
onChange(value.filter(id => id !== tagId))
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
return (
|
|
620
|
+
<div className="space-y-2">
|
|
621
|
+
{/* Selected tags */}
|
|
622
|
+
<div className="flex flex-wrap gap-2">
|
|
623
|
+
{selectedTags.map((tag) => (
|
|
624
|
+
<Badge
|
|
625
|
+
key={tag.id}
|
|
626
|
+
style={{ backgroundColor: tag.color }}
|
|
627
|
+
>
|
|
628
|
+
{tag.name}
|
|
629
|
+
<X
|
|
630
|
+
className="ml-1 h-3 w-3 cursor-pointer"
|
|
631
|
+
onClick={() => removeTag(tag.id)}
|
|
632
|
+
/>
|
|
633
|
+
</Badge>
|
|
634
|
+
))}
|
|
635
|
+
</div>
|
|
636
|
+
|
|
637
|
+
{/* Search and select */}
|
|
638
|
+
<input
|
|
639
|
+
type="text"
|
|
640
|
+
placeholder="Search tags..."
|
|
641
|
+
value={search}
|
|
642
|
+
onChange={(e) => setSearch(e.target.value)}
|
|
643
|
+
className="w-full px-3 py-2 border rounded"
|
|
644
|
+
/>
|
|
645
|
+
|
|
646
|
+
{/* Available tags */}
|
|
647
|
+
{search && (
|
|
648
|
+
<div className="flex flex-wrap gap-2">
|
|
649
|
+
{availableTags.map((tag) => (
|
|
650
|
+
<Badge
|
|
651
|
+
key={tag.id}
|
|
652
|
+
variant="outline"
|
|
653
|
+
className="cursor-pointer"
|
|
654
|
+
onClick={() => addTag(tag.id)}
|
|
655
|
+
>
|
|
656
|
+
+ {tag.name}
|
|
657
|
+
</Badge>
|
|
658
|
+
))}
|
|
659
|
+
</div>
|
|
660
|
+
)}
|
|
661
|
+
</div>
|
|
662
|
+
)
|
|
663
|
+
}
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
**📋 Entity Field Type:**
|
|
667
|
+
|
|
668
|
+
```typescript
|
|
669
|
+
// In entity fields definition
|
|
670
|
+
{
|
|
671
|
+
name: 'categoryId',
|
|
672
|
+
type: 'taxonomy',
|
|
673
|
+
taxonomyType: 'post_category',
|
|
674
|
+
required: true,
|
|
675
|
+
display: {
|
|
676
|
+
label: 'Category',
|
|
677
|
+
showInList: true,
|
|
678
|
+
showInForm: true,
|
|
679
|
+
}
|
|
680
|
+
},
|
|
681
|
+
{
|
|
682
|
+
name: 'tagIds',
|
|
683
|
+
type: 'taxonomy-multi',
|
|
684
|
+
taxonomyType: 'post_tag',
|
|
685
|
+
required: false,
|
|
686
|
+
display: {
|
|
687
|
+
label: 'Tags',
|
|
688
|
+
showInList: true,
|
|
689
|
+
showInForm: true,
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
```
|
|
693
|
+
|
|
694
|
+
```
|
|
695
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
696
|
+
|
|
697
|
+
✅ TUTORIAL STORY!
|
|
698
|
+
|
|
699
|
+
You've learned:
|
|
700
|
+
• Taxonomy system concepts
|
|
701
|
+
• Table structure with hierarchy
|
|
702
|
+
• Creating taxonomy types
|
|
703
|
+
• Entity-taxonomy relationships
|
|
704
|
+
• UI components for selection
|
|
705
|
+
|
|
706
|
+
📚 Related tutorials:
|
|
707
|
+
• /how-to:create-entity - Create entities with taxonomies
|
|
708
|
+
• /how-to:create-migrations - Database setup
|
|
709
|
+
• /how-to:implement-search - Filter by taxonomy
|
|
710
|
+
|
|
711
|
+
🔙 Back to menu: /how-to:start
|
|
712
|
+
```
|
|
713
|
+
|
|
714
|
+
---
|
|
715
|
+
|
|
716
|
+
## Interactive Options
|
|
717
|
+
|
|
718
|
+
### "What's the difference from enum fields?"
|
|
719
|
+
|
|
720
|
+
```
|
|
721
|
+
📋 Taxonomies vs Enum Fields:
|
|
722
|
+
|
|
723
|
+
ENUM FIELDS:
|
|
724
|
+
• Hardcoded in schema
|
|
725
|
+
• Requires code change to modify
|
|
726
|
+
• Type-safe at compile time
|
|
727
|
+
• Limited options (5-10 typically)
|
|
728
|
+
• Example: status: 'draft' | 'published' | 'archived'
|
|
729
|
+
|
|
730
|
+
TAXONOMIES:
|
|
731
|
+
• Stored in database
|
|
732
|
+
• Modified at runtime (admin UI)
|
|
733
|
+
• Unlimited options
|
|
734
|
+
• Can be user-created
|
|
735
|
+
• Support hierarchy
|
|
736
|
+
• Have metadata (icon, color, description)
|
|
737
|
+
• Example: categories, tags, skills
|
|
738
|
+
|
|
739
|
+
USE ENUM FOR:
|
|
740
|
+
✓ Fixed, known options
|
|
741
|
+
✓ Business logic (status, priority)
|
|
742
|
+
✓ Type safety needed
|
|
743
|
+
|
|
744
|
+
USE TAXONOMY FOR:
|
|
745
|
+
✓ User-defined classifications
|
|
746
|
+
✓ Content organization
|
|
747
|
+
✓ Variable number of options
|
|
748
|
+
✓ Need hierarchy
|
|
749
|
+
✓ Visual customization
|
|
750
|
+
```
|
|
751
|
+
|
|
752
|
+
### "How do I handle taxonomy counts?"
|
|
753
|
+
|
|
754
|
+
```
|
|
755
|
+
📋 Taxonomy Counts:
|
|
756
|
+
|
|
757
|
+
Add a count column or compute dynamically:
|
|
758
|
+
|
|
759
|
+
OPTION 1: Computed (always accurate)
|
|
760
|
+
|
|
761
|
+
SELECT
|
|
762
|
+
t.*,
|
|
763
|
+
(
|
|
764
|
+
SELECT COUNT(*)
|
|
765
|
+
FROM entity_taxonomy_relations etr
|
|
766
|
+
WHERE etr.taxonomyId = t.id
|
|
767
|
+
) as postCount
|
|
768
|
+
FROM taxonomies t
|
|
769
|
+
WHERE t.type = 'post_category';
|
|
770
|
+
|
|
771
|
+
OPTION 2: Cached (faster, needs updates)
|
|
772
|
+
|
|
773
|
+
ALTER TABLE taxonomies ADD COLUMN entityCount INTEGER DEFAULT 0;
|
|
774
|
+
|
|
775
|
+
-- Update on insert
|
|
776
|
+
CREATE OR REPLACE FUNCTION update_taxonomy_count()
|
|
777
|
+
RETURNS TRIGGER AS $$
|
|
778
|
+
BEGIN
|
|
779
|
+
IF TG_OP = 'INSERT' THEN
|
|
780
|
+
UPDATE taxonomies
|
|
781
|
+
SET entityCount = entityCount + 1
|
|
782
|
+
WHERE id = NEW.taxonomyId;
|
|
783
|
+
ELSIF TG_OP = 'DELETE' THEN
|
|
784
|
+
UPDATE taxonomies
|
|
785
|
+
SET entityCount = entityCount - 1
|
|
786
|
+
WHERE id = OLD.taxonomyId;
|
|
787
|
+
END IF;
|
|
788
|
+
RETURN NULL;
|
|
789
|
+
END;
|
|
790
|
+
$$ LANGUAGE plpgsql;
|
|
791
|
+
|
|
792
|
+
CREATE TRIGGER taxonomy_count_trigger
|
|
793
|
+
AFTER INSERT OR DELETE ON entity_taxonomy_relations
|
|
794
|
+
FOR EACH ROW EXECUTE FUNCTION update_taxonomy_count();
|
|
795
|
+
```
|
|
796
|
+
|
|
797
|
+
---
|
|
798
|
+
|
|
799
|
+
## Related Commands
|
|
800
|
+
|
|
801
|
+
| Command | Description |
|
|
802
|
+
|---------|-------------|
|
|
803
|
+
| `/how-to:create-entity` | Create entities with taxonomy fields |
|
|
804
|
+
| `/how-to:create-migrations` | Database setup |
|
|
805
|
+
| `/how-to:implement-search` | Filter by taxonomy |
|
|
806
|
+
| `/how-to:create-child-entities` | Taxonomies for child entities |
|