@atlashub/smartstack-cli 3.39.0 → 3.40.0
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/.documentation/apex.html +644 -644
- package/.documentation/css/styles.css +2320 -2320
- package/.documentation/init.html +1377 -1377
- package/.documentation/js/app.js +780 -780
- package/.documentation/prd-json-v2.0.0.md +396 -396
- package/.documentation/testing-ba-e2e.md +462 -462
- package/config/default-config.json +95 -95
- package/config/mcp-defaults.json +62 -62
- package/config/settings.json +53 -53
- package/config/settings.local.example.json +16 -16
- package/dist/index.js.map +1 -1
- package/dist/mcp-entry.mjs +6 -4
- package/dist/mcp-entry.mjs.map +1 -1
- package/package.json +115 -115
- package/scripts/extract-api-endpoints.ts +325 -325
- package/scripts/extract-business-rules.ts +440 -440
- package/scripts/generate-doc-with-mock-ui.ts +804 -804
- package/scripts/health-check.sh +168 -168
- package/scripts/postinstall.js +18 -18
- package/templates/agents/action.md +37 -37
- package/templates/agents/ba-reader.md +378 -378
- package/templates/agents/ba-writer.md +861 -861
- package/templates/agents/code-reviewer.md +163 -163
- package/templates/agents/db-reader.md +149 -149
- package/templates/agents/docs-context-reader.md +143 -143
- package/templates/agents/docs-sync-checker.md +122 -122
- package/templates/agents/efcore/conflicts.md +84 -84
- package/templates/agents/efcore/db-deploy.md +74 -74
- package/templates/agents/efcore/db-reset.md +85 -85
- package/templates/agents/efcore/db-seed.md +61 -61
- package/templates/agents/efcore/db-status.md +86 -86
- package/templates/agents/efcore/migration.md +186 -186
- package/templates/agents/efcore/rebase-snapshot.md +108 -108
- package/templates/agents/efcore/scan.md +92 -92
- package/templates/agents/efcore/squash.md +161 -161
- package/templates/agents/explore-codebase.md +66 -66
- package/templates/agents/explore-docs.md +98 -98
- package/templates/agents/fix-grammar.md +50 -50
- package/templates/agents/gitflow/abort.md +45 -45
- package/templates/agents/gitflow/cleanup.md +96 -96
- package/templates/agents/gitflow/commit.md +236 -236
- package/templates/agents/gitflow/exec.md +48 -48
- package/templates/agents/gitflow/finish.md +146 -146
- package/templates/agents/gitflow/init-clone.md +199 -199
- package/templates/agents/gitflow/init-detect.md +137 -137
- package/templates/agents/gitflow/init-validate.md +225 -225
- package/templates/agents/gitflow/init.md +340 -340
- package/templates/agents/gitflow/merge.md +145 -145
- package/templates/agents/gitflow/plan.md +42 -42
- package/templates/agents/gitflow/pr.md +191 -191
- package/templates/agents/gitflow/review.md +49 -49
- package/templates/agents/gitflow/start.md +147 -147
- package/templates/agents/gitflow/status.md +95 -95
- package/templates/agents/mcp-healthcheck.md +163 -163
- package/templates/agents/snipper.md +37 -37
- package/templates/agents/websearch.md +46 -46
- package/templates/hooks/appsettings-guard.sh +76 -76
- package/templates/hooks/docs-drift-check.md +96 -96
- package/templates/hooks/ef-migration-check.md +139 -139
- package/templates/hooks/hooks.json +58 -58
- package/templates/hooks/mcp-check.md +64 -64
- package/templates/hooks/ralph-mcp-logger.sh +46 -46
- package/templates/hooks/ralph-session-end.sh +69 -69
- package/templates/hooks/stop-hook.sh +177 -177
- package/templates/hooks/wsl-dotnet-cleanup.sh +24 -24
- package/templates/mcp-scaffolding/component.tsx.hbs +318 -318
- package/templates/mcp-scaffolding/controller.cs.hbs +192 -192
- package/templates/mcp-scaffolding/entity-extension.cs.hbs +239 -239
- package/templates/mcp-scaffolding/frontend/api-client.ts.hbs +116 -116
- package/templates/mcp-scaffolding/frontend/nav-routes.ts.hbs +133 -133
- package/templates/mcp-scaffolding/frontend/routes.tsx.hbs +126 -126
- package/templates/mcp-scaffolding/migrations/seed-roles.cs.hbs +261 -261
- package/templates/mcp-scaffolding/service-extension.cs.hbs +53 -53
- package/templates/mcp-scaffolding/tests/controller.test.cs.hbs +436 -436
- package/templates/mcp-scaffolding/tests/entity.test.cs.hbs +239 -239
- package/templates/mcp-scaffolding/tests/repository.test.cs.hbs +441 -441
- package/templates/mcp-scaffolding/tests/security.test.cs.hbs +442 -442
- package/templates/mcp-scaffolding/tests/service.test.cs.hbs +402 -402
- package/templates/mcp-scaffolding/tests/validator.test.cs.hbs +428 -428
- package/templates/project/DependencyInjection.Application.cs.template +25 -25
- package/templates/project/DependencyInjection.Infrastructure.cs.template +61 -61
- package/templates/project/DesignTimeExtensionsDbContextFactory.cs.template +70 -70
- package/templates/project/ExampleEntity.cs.template +116 -116
- package/templates/project/ExampleEntityConfiguration.cs.template +64 -64
- package/templates/project/ExampleService.cs.template +146 -146
- package/templates/project/ExtensionsDbContext.cs.template +41 -41
- package/templates/project/IExtensionsDbContext.cs.template +22 -22
- package/templates/project/Program.cs.template +47 -47
- package/templates/project/README.md +79 -79
- package/templates/project/api.ts.template +12 -12
- package/templates/project/appsettings.json.template +170 -170
- package/templates/project/claude-settings.json.template +5 -5
- package/templates/project/test-frontend/msw/handlers.ts +58 -58
- package/templates/project/test-frontend/msw/server.ts +25 -25
- package/templates/project/test-frontend/setup.ts +16 -16
- package/templates/project/test-frontend/test-utils.tsx +59 -59
- package/templates/project/test-frontend/vitest.config.ts +31 -31
- package/templates/ralph/README.md +93 -93
- package/templates/ralph/ralph.config.yaml +113 -113
- package/templates/scripts/setup-ralph-loop.sh +173 -173
- package/templates/skills/_resources/config-safety.md +61 -61
- package/templates/skills/_resources/context-digest-template.md +53 -53
- package/templates/skills/_resources/doc-context-cache.md +60 -60
- package/templates/skills/_resources/docs-manifest-schema.md +155 -155
- package/templates/skills/_resources/formatting-guide.md +124 -124
- package/templates/skills/_resources/mcp-validate-documentation-spec.md +181 -181
- package/templates/skills/_shared.md +228 -228
- package/templates/skills/admin/SKILL.md +48 -48
- package/templates/skills/ai-prompt/SKILL.md +107 -107
- package/templates/skills/ai-prompt/steps/step-00-init.md +47 -47
- package/templates/skills/ai-prompt/steps/step-01-implementation.md +122 -122
- package/templates/skills/apex/SKILL.md +168 -168
- package/templates/skills/apex/_shared.md +141 -141
- package/templates/skills/apex/references/agent-teams-protocol.md +164 -164
- package/templates/skills/apex/references/analysis-methods.md +141 -141
- package/templates/skills/apex/references/challenge-questions.md +145 -145
- package/templates/skills/apex/references/code-generation.md +412 -412
- package/templates/skills/apex/references/core-seed-data.md +1437 -1437
- package/templates/skills/apex/references/error-classification.md +144 -144
- package/templates/skills/apex/references/examine-build-validation.md +82 -82
- package/templates/skills/apex/references/execution-frontend-gates.md +177 -177
- package/templates/skills/apex/references/execution-frontend-patterns.md +105 -105
- package/templates/skills/apex/references/execution-layer1-rules.md +96 -96
- package/templates/skills/apex/references/initialization-challenge-flow.md +110 -110
- package/templates/skills/apex/references/planning-layer-mapping.md +151 -151
- package/templates/skills/apex/references/post-checks.md +1584 -1584
- package/templates/skills/apex/references/smartstack-api.md +1053 -1053
- package/templates/skills/apex/references/smartstack-frontend.md +1571 -1571
- package/templates/skills/apex/references/smartstack-layers.md +402 -402
- package/templates/skills/apex/steps/step-00-init.md +307 -307
- package/templates/skills/apex/steps/step-01-analyze.md +165 -165
- package/templates/skills/apex/steps/step-02-plan.md +144 -144
- package/templates/skills/apex/steps/step-03-execute.md +328 -328
- package/templates/skills/apex/steps/step-04-examine.md +263 -263
- package/templates/skills/apex/steps/step-05-deep-review.md +129 -129
- package/templates/skills/apex/steps/step-06-resolve.md +101 -101
- package/templates/skills/apex/steps/step-07-tests.md +238 -238
- package/templates/skills/apex/steps/step-08-run-tests.md +125 -125
- package/templates/skills/application/SKILL.md +4 -4
- package/templates/skills/application/references/application-roles-template.md +227 -227
- package/templates/skills/application/references/backend-controller-hierarchy.md +58 -58
- package/templates/skills/application/references/backend-entity-seeding.md +72 -72
- package/templates/skills/application/references/backend-seeding-and-dto-output.md +83 -83
- package/templates/skills/application/references/backend-table-prefix-mapping.md +79 -79
- package/templates/skills/application/references/backend-verification.md +88 -88
- package/templates/skills/application/references/frontend-i18n-and-output.md +67 -67
- package/templates/skills/application/references/frontend-route-naming.md +117 -117
- package/templates/skills/application/references/frontend-route-wiring-app-tsx.md +107 -107
- package/templates/skills/application/references/frontend-verification.md +156 -156
- package/templates/skills/application/references/migration-checklist-troubleshooting.md +1 -1
- package/templates/skills/application/references/provider-template.md +177 -177
- package/templates/skills/application/references/roles-client-project-handling.md +55 -55
- package/templates/skills/application/references/roles-fallback-procedure.md +149 -149
- package/templates/skills/application/references/test-coverage-requirements.md +213 -213
- package/templates/skills/application/references/test-frontend.md +73 -73
- package/templates/skills/application/references/test-prerequisites.md +72 -72
- package/templates/skills/application/steps/step-05-frontend.md +176 -176
- package/templates/skills/application/steps/step-06-migration.md +193 -193
- package/templates/skills/application/steps/step-07-tests.md +356 -356
- package/templates/skills/application/steps/step-08-documentation.md +137 -137
- package/templates/skills/application/templates-backend.md +463 -463
- package/templates/skills/application/templates-frontend.md +685 -685
- package/templates/skills/application/templates-i18n.md +520 -520
- package/templates/skills/application/templates-seed.md +1096 -1096
- package/templates/skills/business-analyse/SKILL.md +327 -327
- package/templates/skills/business-analyse/_architecture.md +123 -123
- package/templates/skills/business-analyse/_elicitation.md +206 -206
- package/templates/skills/business-analyse/_module-loop.md +115 -115
- package/templates/skills/business-analyse/_shared.md +383 -383
- package/templates/skills/business-analyse/_suggestions.md +34 -34
- package/templates/skills/business-analyse/html/ba-interactive.html +4477 -4477
- package/templates/skills/business-analyse/html/build-html.js +77 -77
- package/templates/skills/business-analyse/html/src/scripts/01-data-init.js +150 -150
- package/templates/skills/business-analyse/html/src/scripts/02-navigation.js +227 -227
- package/templates/skills/business-analyse/html/src/scripts/03-render-cadrage.js +199 -199
- package/templates/skills/business-analyse/html/src/scripts/04-render-modules.js +205 -205
- package/templates/skills/business-analyse/html/src/scripts/05-render-specs.js +647 -647
- package/templates/skills/business-analyse/html/src/scripts/06-render-consolidation.js +195 -195
- package/templates/skills/business-analyse/html/src/scripts/07-render-handoff.js +92 -92
- package/templates/skills/business-analyse/html/src/scripts/08-editing.js +135 -135
- package/templates/skills/business-analyse/html/src/scripts/09-export.js +168 -168
- package/templates/skills/business-analyse/html/src/scripts/10-comments.js +171 -171
- package/templates/skills/business-analyse/html/src/scripts/11-review-panel.js +166 -166
- package/templates/skills/business-analyse/html/src/styles/01-variables.css +38 -38
- package/templates/skills/business-analyse/html/src/styles/02-layout.css +101 -101
- package/templates/skills/business-analyse/html/src/styles/03-navigation.css +120 -120
- package/templates/skills/business-analyse/html/src/styles/04-cards.css +196 -196
- package/templates/skills/business-analyse/html/src/styles/05-modules.css +454 -454
- package/templates/skills/business-analyse/html/src/styles/06-wireframes.css +272 -272
- package/templates/skills/business-analyse/html/src/styles/07-comments.css +184 -184
- package/templates/skills/business-analyse/html/src/styles/08-review-panel.css +241 -241
- package/templates/skills/business-analyse/html/src/template.html +516 -516
- package/templates/skills/business-analyse/patterns/suggestion-catalog.md +546 -546
- package/templates/skills/business-analyse/questionnaire/00-application.md +160 -160
- package/templates/skills/business-analyse/questionnaire/00b-project.md +85 -85
- package/templates/skills/business-analyse/questionnaire/01-context.md +185 -185
- package/templates/skills/business-analyse/questionnaire/02-stakeholders.md +189 -189
- package/templates/skills/business-analyse/questionnaire/03-scope.md +164 -164
- package/templates/skills/business-analyse/questionnaire/04-data.md +88 -88
- package/templates/skills/business-analyse/questionnaire/05-integrations.md +58 -58
- package/templates/skills/business-analyse/questionnaire/06-security.md +68 -68
- package/templates/skills/business-analyse/questionnaire/07-ui.md +76 -76
- package/templates/skills/business-analyse/questionnaire/08-performance.md +42 -42
- package/templates/skills/business-analyse/questionnaire/09-constraints.md +45 -45
- package/templates/skills/business-analyse/questionnaire/10-documentation.md +43 -43
- package/templates/skills/business-analyse/questionnaire/11-data-lifecycle.md +59 -59
- package/templates/skills/business-analyse/questionnaire/12-migration.md +58 -58
- package/templates/skills/business-analyse/questionnaire/13-cross-module.md +69 -69
- package/templates/skills/business-analyse/questionnaire/14-risk-assumptions.md +135 -135
- package/templates/skills/business-analyse/questionnaire/15-success-metrics.md +136 -136
- package/templates/skills/business-analyse/questionnaire.md +337 -337
- package/templates/skills/business-analyse/react/application-viewer.md +242 -242
- package/templates/skills/business-analyse/react/components.md +551 -551
- package/templates/skills/business-analyse/react/i18n-template.md +306 -306
- package/templates/skills/business-analyse/references/acceptance-criteria.md +169 -169
- package/templates/skills/business-analyse/references/agent-module-prompt.md +362 -362
- package/templates/skills/business-analyse/references/agent-pooling-best-practices.md +557 -557
- package/templates/skills/business-analyse/references/analysis-semantic-checks.md +190 -190
- package/templates/skills/business-analyse/references/cache-warming-strategy.md +566 -566
- package/templates/skills/business-analyse/references/cadrage-challenge-patterns.md +41 -41
- package/templates/skills/business-analyse/references/cadrage-coverage-matrix.md +74 -74
- package/templates/skills/business-analyse/references/cadrage-pre-analysis.md +115 -115
- package/templates/skills/business-analyse/references/cadrage-shared-modules.md +68 -69
- package/templates/skills/business-analyse/references/cadrage-structure-cards.md +85 -85
- package/templates/skills/business-analyse/references/compilation-structure-cards.md +297 -297
- package/templates/skills/business-analyse/references/consolidation-structural-checks.md +107 -107
- package/templates/skills/business-analyse/references/deploy-data-build.md +180 -180
- package/templates/skills/business-analyse/references/deploy-modes.md +118 -118
- package/templates/skills/business-analyse/references/detection-strategies.md +424 -424
- package/templates/skills/business-analyse/references/entity-architecture-decision.md +218 -218
- package/templates/skills/business-analyse/references/handoff-file-templates.md +120 -120
- package/templates/skills/business-analyse/references/handoff-mappings.md +81 -81
- package/templates/skills/business-analyse/references/handoff-seeddata-generation.md +312 -312
- package/templates/skills/business-analyse/references/html-data-mapping.md +299 -299
- package/templates/skills/business-analyse/references/init-schema-deployment.md +65 -65
- package/templates/skills/business-analyse/references/naming-conventions.md +243 -243
- package/templates/skills/business-analyse/references/prd-generation.md +258 -258
- package/templates/skills/business-analyse/references/review-data-mapping.md +363 -363
- package/templates/skills/business-analyse/references/robustness-checks.md +542 -542
- package/templates/skills/business-analyse/references/spec-auto-inference.md +111 -111
- package/templates/skills/business-analyse/references/team-orchestration.md +1022 -1022
- package/templates/skills/business-analyse/references/ui-dashboard-spec.md +85 -85
- package/templates/skills/business-analyse/references/ui-resource-cards.md +259 -259
- package/templates/skills/business-analyse/references/validate-incremental-html.md +121 -121
- package/templates/skills/business-analyse/references/validation-checklist.md +347 -347
- package/templates/skills/business-analyse/references/wireframe-svg-style-guide.md +335 -335
- package/templates/skills/business-analyse/schemas/application-schema.json +453 -453
- package/templates/skills/business-analyse/schemas/feature-schema.json +53 -53
- package/templates/skills/business-analyse/schemas/project-schema.json +485 -485
- package/templates/skills/business-analyse/schemas/sections/analysis-schema.json +201 -201
- package/templates/skills/business-analyse/schemas/sections/discovery-schema.json +82 -82
- package/templates/skills/business-analyse/schemas/sections/handoff-schema.json +80 -80
- package/templates/skills/business-analyse/schemas/sections/metadata-schema.json +70 -70
- package/templates/skills/business-analyse/schemas/sections/specification-schema.json +547 -547
- package/templates/skills/business-analyse/schemas/sections/validation-schema.json +93 -93
- package/templates/skills/business-analyse/schemas/shared/common-defs.json +226 -226
- package/templates/skills/business-analyse/steps/step-00-init.md +575 -576
- package/templates/skills/business-analyse/steps/step-01-cadrage.md +767 -767
- package/templates/skills/business-analyse/steps/step-01b-applications.md +419 -419
- package/templates/skills/business-analyse/steps/step-02-decomposition.md +387 -387
- package/templates/skills/business-analyse/steps/step-03a-data.md +16 -16
- package/templates/skills/business-analyse/steps/step-03a1-setup.md +506 -506
- package/templates/skills/business-analyse/steps/step-03a2-analysis.md +252 -252
- package/templates/skills/business-analyse/steps/step-03b-ui.md +425 -425
- package/templates/skills/business-analyse/steps/step-03c-compile.md +611 -611
- package/templates/skills/business-analyse/steps/step-03d-validate.md +783 -783
- package/templates/skills/business-analyse/steps/step-04-consolidation.md +17 -17
- package/templates/skills/business-analyse/steps/step-04a-collect.md +415 -415
- package/templates/skills/business-analyse/steps/step-04b-analyze.md +163 -163
- package/templates/skills/business-analyse/steps/step-04c-decide.md +186 -186
- package/templates/skills/business-analyse/steps/step-05a-handoff.md +840 -840
- package/templates/skills/business-analyse/steps/step-05b-deploy.md +522 -522
- package/templates/skills/business-analyse/steps/step-05c-ralph-readiness.md +703 -703
- package/templates/skills/business-analyse/steps/step-06-review.md +278 -278
- package/templates/skills/business-analyse/templates/tpl-frd.md +168 -168
- package/templates/skills/business-analyse/templates/tpl-handoff.md +186 -186
- package/templates/skills/business-analyse/templates/tpl-launch-displays.md +59 -59
- package/templates/skills/business-analyse/templates/tpl-progress.md +172 -172
- package/templates/skills/business-analyse/templates-frd.md +476 -476
- package/templates/skills/business-analyse/templates-react.md +574 -574
- package/templates/skills/cc-agent/SKILL.md +129 -129
- package/templates/skills/cc-agent/references/agent-behavior-patterns.md +95 -95
- package/templates/skills/cc-agent/references/agent-frontmatter.md +213 -213
- package/templates/skills/cc-agent/references/permission-modes.md +102 -102
- package/templates/skills/cc-agent/references/tools-reference.md +144 -144
- package/templates/skills/cc-agent/steps/step-00-init.md +134 -134
- package/templates/skills/cc-agent/steps/step-01-design.md +186 -186
- package/templates/skills/cc-agent/steps/step-02-generate.md +131 -131
- package/templates/skills/cc-agent/steps/step-03-validate.md +130 -130
- package/templates/skills/cc-agent/templates/agent-categorized.md +67 -67
- package/templates/skills/cc-agent/templates/agent-standalone.md +56 -56
- package/templates/skills/cc-agent/templates/agent-with-skills.md +94 -94
- package/templates/skills/cc-audit/SKILL.md +108 -108
- package/templates/skills/cc-audit/references/agent-checklist.md +91 -91
- package/templates/skills/cc-audit/references/hook-checklist.md +110 -110
- package/templates/skills/cc-audit/references/skill-checklist.md +70 -70
- package/templates/skills/cc-audit/steps/step-00-init.md +98 -98
- package/templates/skills/cc-audit/steps/step-01-scan.md +142 -142
- package/templates/skills/cc-audit/steps/step-02-analyze.md +158 -158
- package/templates/skills/cc-audit/steps/step-03-report.md +142 -142
- package/templates/skills/cc-skill/SKILL.md +134 -134
- package/templates/skills/cc-skill/references/best-practices.md +167 -167
- package/templates/skills/cc-skill/references/frontmatter-reference.md +182 -182
- package/templates/skills/cc-skill/references/skill-patterns.md +199 -199
- package/templates/skills/cc-skill/steps/step-00-init.md +119 -119
- package/templates/skills/cc-skill/steps/step-01-design.md +199 -199
- package/templates/skills/cc-skill/steps/step-02-generate.md +145 -145
- package/templates/skills/cc-skill/steps/step-03-steps.md +151 -151
- package/templates/skills/cc-skill/steps/step-04-validate.md +124 -124
- package/templates/skills/cc-skill/templates/skill-forked.md +85 -85
- package/templates/skills/cc-skill/templates/skill-progressive.md +102 -102
- package/templates/skills/cc-skill/templates/skill-simple.md +75 -75
- package/templates/skills/cc-skill/templates/step-template.md +82 -82
- package/templates/skills/check-version/SKILL.md +196 -196
- package/templates/skills/controller/SKILL.md +162 -162
- package/templates/skills/controller/postman-templates.md +614 -614
- package/templates/skills/controller/references/controller-code-templates.md +159 -159
- package/templates/skills/controller/references/mcp-scaffold-workflow.md +209 -209
- package/templates/skills/controller/references/permission-sync-templates.md +149 -149
- package/templates/skills/controller/steps/step-00-init.md +193 -191
- package/templates/skills/controller/steps/step-01-analyze.md +146 -146
- package/templates/skills/controller/steps/step-02-plan.md +176 -176
- package/templates/skills/controller/steps/step-03-generate.md +189 -189
- package/templates/skills/controller/steps/step-04-perms.md +80 -80
- package/templates/skills/controller/steps/step-05-validate.md +107 -107
- package/templates/skills/controller/templates.md +1555 -1555
- package/templates/skills/debug/SKILL.md +70 -70
- package/templates/skills/debug/references/team-protocol.md +232 -232
- package/templates/skills/debug/steps/step-00-init.md +57 -57
- package/templates/skills/debug/steps/step-01-analyze.md +219 -219
- package/templates/skills/debug/steps/step-02-resolve.md +85 -85
- package/templates/skills/documentation/SKILL.md +132 -132
- package/templates/skills/documentation/data-schema.md +227 -227
- package/templates/skills/documentation/steps/step-00-init.md +70 -70
- package/templates/skills/documentation/steps/step-01-scan.md +113 -113
- package/templates/skills/documentation/steps/step-02-generate.md +231 -231
- package/templates/skills/documentation/steps/step-03-validate.md +251 -238
- package/templates/skills/documentation/templates.md +662 -663
- package/templates/skills/efcore/SKILL.md +167 -167
- package/templates/skills/efcore/references/both-contexts.md +32 -32
- package/templates/skills/efcore/references/database-operations.md +67 -67
- package/templates/skills/efcore/references/destructive-operations.md +38 -38
- package/templates/skills/efcore/references/reset-operations.md +81 -81
- package/templates/skills/efcore/references/seed-methods.md +86 -86
- package/templates/skills/efcore/references/shared-init-functions.md +250 -250
- package/templates/skills/efcore/references/sql-objects-injection.md +61 -61
- package/templates/skills/efcore/references/troubleshooting.md +81 -81
- package/templates/skills/efcore/references/zero-downtime-patterns.md +227 -227
- package/templates/skills/efcore/steps/db/step-deploy.md +217 -217
- package/templates/skills/efcore/steps/db/step-reset.md +186 -186
- package/templates/skills/efcore/steps/db/step-seed.md +166 -166
- package/templates/skills/efcore/steps/db/step-status.md +173 -173
- package/templates/skills/efcore/steps/migration/step-00-init.md +102 -102
- package/templates/skills/efcore/steps/migration/step-01-check.md +164 -164
- package/templates/skills/efcore/steps/migration/step-02-create.md +160 -160
- package/templates/skills/efcore/steps/migration/step-03-validate.md +168 -168
- package/templates/skills/efcore/steps/rebase-snapshot/step-00-init.md +173 -173
- package/templates/skills/efcore/steps/rebase-snapshot/step-01-backup.md +100 -100
- package/templates/skills/efcore/steps/rebase-snapshot/step-02-fetch.md +115 -115
- package/templates/skills/efcore/steps/rebase-snapshot/step-03-create.md +112 -112
- package/templates/skills/efcore/steps/rebase-snapshot/step-04-validate.md +157 -157
- package/templates/skills/efcore/steps/shared/step-00-init.md +131 -131
- package/templates/skills/efcore/steps/squash/step-00-init.md +141 -141
- package/templates/skills/efcore/steps/squash/step-01-backup.md +120 -120
- package/templates/skills/efcore/steps/squash/step-02-fetch.md +168 -168
- package/templates/skills/efcore/steps/squash/step-03-create.md +184 -184
- package/templates/skills/efcore/steps/squash/step-04-validate.md +174 -174
- package/templates/skills/explore/SKILL.md +98 -98
- package/templates/skills/feature-full/SKILL.md +111 -111
- package/templates/skills/feature-full/steps/step-00-init.md +57 -57
- package/templates/skills/feature-full/steps/step-01-implementation.md +120 -120
- package/templates/skills/gitflow/SKILL.md +377 -377
- package/templates/skills/gitflow/_shared.md +620 -620
- package/templates/skills/gitflow/phases/abort.md +189 -189
- package/templates/skills/gitflow/phases/cleanup.md +234 -234
- package/templates/skills/gitflow/phases/status.md +192 -192
- package/templates/skills/gitflow/references/commit-message-generation.md +58 -58
- package/templates/skills/gitflow/references/commit-migration-validation.md +49 -49
- package/templates/skills/gitflow/references/finish-cleanup.md +55 -55
- package/templates/skills/gitflow/references/finish-version-bumping.md +45 -45
- package/templates/skills/gitflow/references/init-config-template.md +135 -135
- package/templates/skills/gitflow/references/init-environment-detection.md +41 -41
- package/templates/skills/gitflow/references/init-name-normalization.md +103 -103
- package/templates/skills/gitflow/references/init-questions.md +185 -185
- package/templates/skills/gitflow/references/init-structure-creation.md +75 -75
- package/templates/skills/gitflow/references/init-version-detection.md +21 -21
- package/templates/skills/gitflow/references/init-workspace-detection.md +43 -43
- package/templates/skills/gitflow/references/merge-ci-status.md +36 -36
- package/templates/skills/gitflow/references/merge-execution.md +62 -62
- package/templates/skills/gitflow/references/merge-pr-context.md +76 -76
- package/templates/skills/gitflow/references/plan-template.md +69 -69
- package/templates/skills/gitflow/references/pr-build-checks.md +60 -60
- package/templates/skills/gitflow/references/pr-generation.md +58 -58
- package/templates/skills/gitflow/references/start-branch-normalization.md +28 -28
- package/templates/skills/gitflow/references/start-efcore-preflight.md +70 -70
- package/templates/skills/gitflow/references/start-local-config.md +113 -113
- package/templates/skills/gitflow/references/start-worktree-creation.md +50 -50
- package/templates/skills/gitflow/references/sync-push-verify.md +44 -44
- package/templates/skills/gitflow/references/sync-rebase-conflicts.md +38 -38
- package/templates/skills/gitflow/steps/step-commit.md +199 -199
- package/templates/skills/gitflow/steps/step-finish.md +147 -147
- package/templates/skills/gitflow/steps/step-init.md +190 -190
- package/templates/skills/gitflow/steps/step-merge.md +85 -85
- package/templates/skills/gitflow/steps/step-plan.md +151 -151
- package/templates/skills/gitflow/steps/step-pr.md +199 -199
- package/templates/skills/gitflow/steps/step-start.md +195 -195
- package/templates/skills/gitflow/steps/step-sync.md +161 -161
- package/templates/skills/gitflow/templates/config.json +72 -72
- package/templates/skills/mcp/SKILL.md +62 -62
- package/templates/skills/mcp/steps/step-01-healthcheck.md +108 -108
- package/templates/skills/mcp/steps/step-02-tools.md +73 -73
- package/templates/skills/notification/SKILL.md +173 -173
- package/templates/skills/quick-search/SKILL.md +99 -99
- package/templates/skills/ralph-loop/SKILL.md +234 -234
- package/templates/skills/ralph-loop/references/category-completeness.md +185 -185
- package/templates/skills/ralph-loop/references/category-rules.md +96 -96
- package/templates/skills/ralph-loop/references/compact-loop.md +300 -300
- package/templates/skills/ralph-loop/references/init-resume-recovery.md +127 -127
- package/templates/skills/ralph-loop/references/module-transition.md +151 -151
- package/templates/skills/ralph-loop/references/multi-module-queue.md +171 -171
- package/templates/skills/ralph-loop/references/parallel-execution.md +246 -246
- package/templates/skills/ralph-loop/references/section-splitting.md +439 -439
- package/templates/skills/ralph-loop/references/task-transform-legacy.md +256 -256
- package/templates/skills/ralph-loop/references/team-orchestration.md +547 -547
- package/templates/skills/ralph-loop/steps/step-00-init.md +150 -150
- package/templates/skills/ralph-loop/steps/step-01-task.md +174 -174
- package/templates/skills/ralph-loop/steps/step-02-execute.md +177 -177
- package/templates/skills/ralph-loop/steps/step-03-commit.md +92 -92
- package/templates/skills/ralph-loop/steps/step-04-check.md +207 -207
- package/templates/skills/ralph-loop/steps/step-05-report.md +175 -175
- package/templates/skills/refactor/SKILL.md +56 -56
- package/templates/skills/refactor/steps/step-01-discover.md +60 -60
- package/templates/skills/refactor/steps/step-02-execute.md +67 -67
- package/templates/skills/review-code/SKILL.md +94 -94
- package/templates/skills/review-code/references/clean-code-principles.md +292 -292
- package/templates/skills/review-code/references/code-quality-metrics.md +174 -174
- package/templates/skills/review-code/references/feedback-patterns.md +149 -149
- package/templates/skills/review-code/references/owasp-api-top10.md +243 -243
- package/templates/skills/review-code/references/security-checklist.md +212 -212
- package/templates/skills/review-code/steps/step-01-smartstack.md +96 -96
- package/templates/skills/review-code/steps/step-02-detailed-review.md +80 -80
- package/templates/skills/review-code/steps/step-03-react.md +44 -44
- package/templates/skills/ui-components/SKILL.md +137 -137
- package/templates/skills/ui-components/accessibility.md +170 -170
- package/templates/skills/ui-components/patterns/dashboard-chart.md +327 -327
- package/templates/skills/ui-components/patterns/data-table.md +39 -39
- package/templates/skills/ui-components/patterns/entity-card.md +77 -77
- package/templates/skills/ui-components/patterns/grid-layout.md +91 -91
- package/templates/skills/ui-components/patterns/kanban.md +43 -43
- package/templates/skills/ui-components/responsive-guidelines.md +278 -278
- package/templates/skills/ui-components/style-guide.md +113 -113
- package/templates/skills/utils/SKILL.md +44 -44
- package/templates/skills/utils/subcommands/test-web-config.md +152 -152
- package/templates/skills/utils/subcommands/test-web.md +123 -123
- package/templates/skills/validate/SKILL.md +181 -181
- package/templates/skills/validate-feature/SKILL.md +101 -101
- package/templates/skills/validate-feature/references/api-smoke-tests.md +140 -140
- package/templates/skills/validate-feature/references/db-validation-checks.md +180 -180
- package/templates/skills/validate-feature/steps/step-00-dependencies.md +121 -121
- package/templates/skills/validate-feature/steps/step-01-compile.md +39 -39
- package/templates/skills/validate-feature/steps/step-02-unit-tests.md +45 -45
- package/templates/skills/validate-feature/steps/step-03-integration-tests.md +53 -53
- package/templates/skills/validate-feature/steps/step-04-api-smoke.md +94 -94
- package/templates/skills/validate-feature/steps/step-05-db-validation.md +149 -149
- package/templates/skills/workflow/SKILL.md +127 -127
- package/templates/skills/workflow/steps/step-00-init.md +57 -57
- package/templates/skills/workflow/steps/step-01-implementation.md +84 -84
- package/templates/test-web/api-health.json +38 -38
- package/templates/test-web/minimal.json +19 -19
- package/templates/test-web/npm-package.json +46 -46
- package/templates/test-web/seo-check.json +54 -54
|
@@ -1,1096 +1,1096 @@
|
|
|
1
|
-
# Templates DB Seed - Application Skill
|
|
2
|
-
|
|
3
|
-
> These templates generate EF Core seeds for navigation entities and their translations.
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## GUID GENERATION RULES
|
|
8
|
-
|
|
9
|
-
```csharp
|
|
10
|
-
// NEVER generate sequential GUIDs with NewGuid()
|
|
11
|
-
// ALWAYS use the deterministic method
|
|
12
|
-
|
|
13
|
-
private static Guid GenerateGuid(int index)
|
|
14
|
-
{
|
|
15
|
-
// Format: 11111111-1111-1111-1111-{index:D12}
|
|
16
|
-
return Guid.Parse($"11111111-1111-1111-1111-{index:D12}");
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// For existing seeds, continue the sequence
|
|
20
|
-
// Check the last index used in NavigationTranslationConfiguration.cs
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
---
|
|
24
|
-
|
|
25
|
-
---
|
|
26
|
-
|
|
27
|
-
## TEMPLATE: APPLICATION
|
|
28
|
-
|
|
29
|
-
### Navigation Entity
|
|
30
|
-
|
|
31
|
-
```csharp
|
|
32
|
-
// In NavigationApplicationConfiguration.cs - HasData()
|
|
33
|
-
new {
|
|
34
|
-
Id = Guid.Parse("$APP_GUID"),
|
|
35
|
-
Code = "$CODE", // e.g.: "sales"
|
|
36
|
-
Label = "$LABEL_EN", // e.g.: "Sales"
|
|
37
|
-
Description = "$DESC_EN", // e.g.: "Sales management"
|
|
38
|
-
Icon = "$ICON", // e.g.: "TrendingUp"
|
|
39
|
-
IconType = IconType.Lucide,
|
|
40
|
-
Route = "/$CODE", // e.g.: "/sales"
|
|
41
|
-
DisplayOrder = $ORDER,
|
|
42
|
-
IsActive = true,
|
|
43
|
-
CreatedAt = seedDate
|
|
44
|
-
}
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
### Translations (4 languages)
|
|
48
|
-
|
|
49
|
-
```csharp
|
|
50
|
-
// Application translations
|
|
51
|
-
translations.Add(new {
|
|
52
|
-
Id = GenerateGuid(index++),
|
|
53
|
-
EntityType = NavigationEntityType.Application,
|
|
54
|
-
EntityId = $APP_GUID,
|
|
55
|
-
LanguageCode = "fr",
|
|
56
|
-
Label = "$LABEL_FR",
|
|
57
|
-
Description = "$DESC_FR",
|
|
58
|
-
CreatedAt = seedDate
|
|
59
|
-
});
|
|
60
|
-
translations.Add(new {
|
|
61
|
-
Id = GenerateGuid(index++),
|
|
62
|
-
EntityType = NavigationEntityType.Application,
|
|
63
|
-
EntityId = $APP_GUID,
|
|
64
|
-
LanguageCode = "en",
|
|
65
|
-
Label = "$LABEL_EN",
|
|
66
|
-
Description = "$DESC_EN",
|
|
67
|
-
CreatedAt = seedDate
|
|
68
|
-
});
|
|
69
|
-
translations.Add(new {
|
|
70
|
-
Id = GenerateGuid(index++),
|
|
71
|
-
EntityType = NavigationEntityType.Application,
|
|
72
|
-
EntityId = $APP_GUID,
|
|
73
|
-
LanguageCode = "it",
|
|
74
|
-
Label = "$LABEL_IT",
|
|
75
|
-
Description = "$DESC_IT",
|
|
76
|
-
CreatedAt = seedDate
|
|
77
|
-
});
|
|
78
|
-
translations.Add(new {
|
|
79
|
-
Id = GenerateGuid(index++),
|
|
80
|
-
EntityType = NavigationEntityType.Application,
|
|
81
|
-
EntityId = $APP_GUID,
|
|
82
|
-
LanguageCode = "de",
|
|
83
|
-
Label = "$LABEL_DE",
|
|
84
|
-
Description = "$DESC_DE",
|
|
85
|
-
CreatedAt = seedDate
|
|
86
|
-
});
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
---
|
|
90
|
-
|
|
91
|
-
## TEMPLATE: MODULE
|
|
92
|
-
|
|
93
|
-
### Navigation Entity
|
|
94
|
-
|
|
95
|
-
```csharp
|
|
96
|
-
// In NavigationModuleConfiguration.cs - HasData()
|
|
97
|
-
new {
|
|
98
|
-
Id = Guid.Parse("$MODULE_GUID"),
|
|
99
|
-
ApplicationId = Guid.Parse("$APP_GUID"),
|
|
100
|
-
Code = "$CODE", // e.g.: "products"
|
|
101
|
-
Label = "$LABEL_EN", // e.g.: "Products"
|
|
102
|
-
Description = "$DESC_EN", // e.g.: "Product management"
|
|
103
|
-
Icon = "$ICON", // e.g.: "Package"
|
|
104
|
-
IconType = IconType.Lucide,
|
|
105
|
-
Route = "/$APP/$CODE", // e.g.: "/sales/products"
|
|
106
|
-
DisplayOrder = $ORDER,
|
|
107
|
-
IsActive = true,
|
|
108
|
-
CreatedAt = seedDate
|
|
109
|
-
}
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
### Translations (4 languages)
|
|
113
|
-
|
|
114
|
-
```csharp
|
|
115
|
-
// Module translations
|
|
116
|
-
translations.Add(new {
|
|
117
|
-
Id = GenerateGuid(index++),
|
|
118
|
-
EntityType = NavigationEntityType.Module,
|
|
119
|
-
EntityId = $MODULE_GUID,
|
|
120
|
-
LanguageCode = "fr",
|
|
121
|
-
Label = "$LABEL_FR",
|
|
122
|
-
Description = "$DESC_FR",
|
|
123
|
-
CreatedAt = seedDate
|
|
124
|
-
});
|
|
125
|
-
translations.Add(new {
|
|
126
|
-
Id = GenerateGuid(index++),
|
|
127
|
-
EntityType = NavigationEntityType.Module,
|
|
128
|
-
EntityId = $MODULE_GUID,
|
|
129
|
-
LanguageCode = "en",
|
|
130
|
-
Label = "$LABEL_EN",
|
|
131
|
-
Description = "$DESC_EN",
|
|
132
|
-
CreatedAt = seedDate
|
|
133
|
-
});
|
|
134
|
-
translations.Add(new {
|
|
135
|
-
Id = GenerateGuid(index++),
|
|
136
|
-
EntityType = NavigationEntityType.Module,
|
|
137
|
-
EntityId = $MODULE_GUID,
|
|
138
|
-
LanguageCode = "it",
|
|
139
|
-
Label = "$LABEL_IT",
|
|
140
|
-
Description = "$DESC_IT",
|
|
141
|
-
CreatedAt = seedDate
|
|
142
|
-
});
|
|
143
|
-
translations.Add(new {
|
|
144
|
-
Id = GenerateGuid(index++),
|
|
145
|
-
EntityType = NavigationEntityType.Module,
|
|
146
|
-
EntityId = $MODULE_GUID,
|
|
147
|
-
LanguageCode = "de",
|
|
148
|
-
Label = "$LABEL_DE",
|
|
149
|
-
Description = "$DESC_DE",
|
|
150
|
-
CreatedAt = seedDate
|
|
151
|
-
});
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
---
|
|
155
|
-
|
|
156
|
-
## TEMPLATE: SECTION
|
|
157
|
-
|
|
158
|
-
### Navigation Entity
|
|
159
|
-
|
|
160
|
-
```csharp
|
|
161
|
-
// In NavigationSectionConfiguration.cs - HasData()
|
|
162
|
-
new {
|
|
163
|
-
Id = Guid.Parse("$SECTION_GUID"),
|
|
164
|
-
ModuleId = Guid.Parse("$MODULE_GUID"),
|
|
165
|
-
Code = "$CODE", // e.g.: "inventory"
|
|
166
|
-
Label = "$LABEL_EN", // e.g.: "Inventory"
|
|
167
|
-
Description = "$DESC_EN", // e.g.: "Inventory management"
|
|
168
|
-
Icon = "$ICON", // e.g.: "Warehouse"
|
|
169
|
-
IconType = IconType.Lucide,
|
|
170
|
-
Route = "/$APP/$MODULE/$CODE", // e.g.: "/sales/products/inventory"
|
|
171
|
-
DisplayOrder = $ORDER,
|
|
172
|
-
IsActive = true,
|
|
173
|
-
CreatedAt = seedDate
|
|
174
|
-
}
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
### Translations (4 languages)
|
|
178
|
-
|
|
179
|
-
```csharp
|
|
180
|
-
// Section translations
|
|
181
|
-
translations.Add(new {
|
|
182
|
-
Id = GenerateGuid(index++),
|
|
183
|
-
EntityType = NavigationEntityType.Section,
|
|
184
|
-
EntityId = $SECTION_GUID,
|
|
185
|
-
LanguageCode = "fr",
|
|
186
|
-
Label = "$LABEL_FR",
|
|
187
|
-
Description = "$DESC_FR",
|
|
188
|
-
CreatedAt = seedDate
|
|
189
|
-
});
|
|
190
|
-
translations.Add(new {
|
|
191
|
-
Id = GenerateGuid(index++),
|
|
192
|
-
EntityType = NavigationEntityType.Section,
|
|
193
|
-
EntityId = $SECTION_GUID,
|
|
194
|
-
LanguageCode = "en",
|
|
195
|
-
Label = "$LABEL_EN",
|
|
196
|
-
Description = "$DESC_EN",
|
|
197
|
-
CreatedAt = seedDate
|
|
198
|
-
});
|
|
199
|
-
translations.Add(new {
|
|
200
|
-
Id = GenerateGuid(index++),
|
|
201
|
-
EntityType = NavigationEntityType.Section,
|
|
202
|
-
EntityId = $SECTION_GUID,
|
|
203
|
-
LanguageCode = "it",
|
|
204
|
-
Label = "$LABEL_IT",
|
|
205
|
-
Description = "$DESC_IT",
|
|
206
|
-
CreatedAt = seedDate
|
|
207
|
-
});
|
|
208
|
-
translations.Add(new {
|
|
209
|
-
Id = GenerateGuid(index++),
|
|
210
|
-
EntityType = NavigationEntityType.Section,
|
|
211
|
-
EntityId = $SECTION_GUID,
|
|
212
|
-
LanguageCode = "de",
|
|
213
|
-
Label = "$LABEL_DE",
|
|
214
|
-
Description = "$DESC_DE",
|
|
215
|
-
CreatedAt = seedDate
|
|
216
|
-
});
|
|
217
|
-
```
|
|
218
|
-
|
|
219
|
-
---
|
|
220
|
-
|
|
221
|
-
## TEMPLATE: PERMISSIONS SEED
|
|
222
|
-
|
|
223
|
-
### Permission Entity
|
|
224
|
-
|
|
225
|
-
```csharp
|
|
226
|
-
// In PermissionConfiguration.cs - HasData()
|
|
227
|
-
// For each CRUD action + assign + execute
|
|
228
|
-
|
|
229
|
-
// Read
|
|
230
|
-
new {
|
|
231
|
-
Id = Guid.Parse("$PERM_READ_GUID"),
|
|
232
|
-
Code = "$APP.$MODULE.read",
|
|
233
|
-
Name = "View $MODULE_LABEL",
|
|
234
|
-
Description = "View $MODULE_LABEL list and details",
|
|
235
|
-
Category = "$APP.$MODULE",
|
|
236
|
-
CreatedAt = seedDate
|
|
237
|
-
},
|
|
238
|
-
// Create
|
|
239
|
-
new {
|
|
240
|
-
Id = Guid.Parse("$PERM_CREATE_GUID"),
|
|
241
|
-
Code = "$APP.$MODULE.create",
|
|
242
|
-
Name = "Create $MODULE_LABEL",
|
|
243
|
-
Description = "Create new $MODULE_LABEL",
|
|
244
|
-
Category = "$APP.$MODULE",
|
|
245
|
-
CreatedAt = seedDate
|
|
246
|
-
},
|
|
247
|
-
// Update
|
|
248
|
-
new {
|
|
249
|
-
Id = Guid.Parse("$PERM_UPDATE_GUID"),
|
|
250
|
-
Code = "$APP.$MODULE.update",
|
|
251
|
-
Name = "Update $MODULE_LABEL",
|
|
252
|
-
Description = "Modify existing $MODULE_LABEL",
|
|
253
|
-
Category = "$APP.$MODULE",
|
|
254
|
-
CreatedAt = seedDate
|
|
255
|
-
},
|
|
256
|
-
// Delete
|
|
257
|
-
new {
|
|
258
|
-
Id = Guid.Parse("$PERM_DELETE_GUID"),
|
|
259
|
-
Code = "$APP.$MODULE.delete",
|
|
260
|
-
Name = "Delete $MODULE_LABEL",
|
|
261
|
-
Description = "Remove $MODULE_LABEL",
|
|
262
|
-
Category = "$APP.$MODULE",
|
|
263
|
-
CreatedAt = seedDate
|
|
264
|
-
}
|
|
265
|
-
```
|
|
266
|
-
|
|
267
|
-
### RolePermission Seed (Default Roles)
|
|
268
|
-
|
|
269
|
-
```csharp
|
|
270
|
-
// In RolePermissionConfiguration.cs - HasData()
|
|
271
|
-
// SuperAdmin gets all permissions automatically (wildcard)
|
|
272
|
-
|
|
273
|
-
// PlatformAdmin - if platform context
|
|
274
|
-
new { RoleId = Guid.Parse("...PlatformAdminRoleId..."), PermissionId = $PERM_READ_GUID },
|
|
275
|
-
new { RoleId = Guid.Parse("...PlatformAdminRoleId..."), PermissionId = $PERM_CREATE_GUID },
|
|
276
|
-
new { RoleId = Guid.Parse("...PlatformAdminRoleId..."), PermissionId = $PERM_UPDATE_GUID },
|
|
277
|
-
new { RoleId = Guid.Parse("...PlatformAdminRoleId..."), PermissionId = $PERM_DELETE_GUID },
|
|
278
|
-
|
|
279
|
-
// StandardUser - read only by default
|
|
280
|
-
new { RoleId = Guid.Parse("...StandardUserRoleId..."), PermissionId = $PERM_READ_GUID }
|
|
281
|
-
```
|
|
282
|
-
|
|
283
|
-
---
|
|
284
|
-
|
|
285
|
-
## TEMPLATE: SECTION PERMISSIONS SEED (Level 3)
|
|
286
|
-
|
|
287
|
-
> **Usage:** When a Module has sub-pages with different permissions
|
|
288
|
-
|
|
289
|
-
### Permission Entity - Section Level
|
|
290
|
-
|
|
291
|
-
```csharp
|
|
292
|
-
// In PermissionConfiguration.cs - HasData()
|
|
293
|
-
// Pattern: {application}.{module}.{section}.{action}
|
|
294
|
-
|
|
295
|
-
// Declare SectionId (must match NavigationSectionConfiguration.cs)
|
|
296
|
-
var {section}SectionId = Guid.Parse("$SECTION_GUID");
|
|
297
|
-
|
|
298
|
-
// Wildcard Section
|
|
299
|
-
new {
|
|
300
|
-
Id = Guid.Parse("$PERM_SECTION_WILDCARD_GUID"),
|
|
301
|
-
Path = "$APP.$MODULE.$SECTION.*",
|
|
302
|
-
Level = PermissionLevel.Section,
|
|
303
|
-
IsWildcard = true,
|
|
304
|
-
SectionId = {section}SectionId,
|
|
305
|
-
Description = "Full $SECTION_LABEL access",
|
|
306
|
-
CreatedAt = seedDate
|
|
307
|
-
},
|
|
308
|
-
// Read
|
|
309
|
-
new {
|
|
310
|
-
Id = Guid.Parse("$PERM_SECTION_READ_GUID"),
|
|
311
|
-
Path = "$APP.$MODULE.$SECTION.read",
|
|
312
|
-
Level = PermissionLevel.Section,
|
|
313
|
-
Action = PermissionAction.Read,
|
|
314
|
-
IsWildcard = false,
|
|
315
|
-
SectionId = {section}SectionId,
|
|
316
|
-
Description = "View $SECTION_LABEL",
|
|
317
|
-
CreatedAt = seedDate
|
|
318
|
-
},
|
|
319
|
-
// Create
|
|
320
|
-
new {
|
|
321
|
-
Id = Guid.Parse("$PERM_SECTION_CREATE_GUID"),
|
|
322
|
-
Path = "$APP.$MODULE.$SECTION.create",
|
|
323
|
-
Level = PermissionLevel.Section,
|
|
324
|
-
Action = PermissionAction.Create,
|
|
325
|
-
IsWildcard = false,
|
|
326
|
-
SectionId = {section}SectionId,
|
|
327
|
-
Description = "Create in $SECTION_LABEL",
|
|
328
|
-
CreatedAt = seedDate
|
|
329
|
-
},
|
|
330
|
-
// Update
|
|
331
|
-
new {
|
|
332
|
-
Id = Guid.Parse("$PERM_SECTION_UPDATE_GUID"),
|
|
333
|
-
Path = "$APP.$MODULE.$SECTION.update",
|
|
334
|
-
Level = PermissionLevel.Section,
|
|
335
|
-
Action = PermissionAction.Update,
|
|
336
|
-
IsWildcard = false,
|
|
337
|
-
SectionId = {section}SectionId,
|
|
338
|
-
Description = "Update in $SECTION_LABEL",
|
|
339
|
-
CreatedAt = seedDate
|
|
340
|
-
},
|
|
341
|
-
// Delete
|
|
342
|
-
new {
|
|
343
|
-
Id = Guid.Parse("$PERM_SECTION_DELETE_GUID"),
|
|
344
|
-
Path = "$APP.$MODULE.$SECTION.delete",
|
|
345
|
-
Level = PermissionLevel.Section,
|
|
346
|
-
Action = PermissionAction.Delete,
|
|
347
|
-
IsWildcard = false,
|
|
348
|
-
SectionId = {section}SectionId,
|
|
349
|
-
Description = "Delete in $SECTION_LABEL",
|
|
350
|
-
CreatedAt = seedDate
|
|
351
|
-
},
|
|
352
|
-
// Execute (optional)
|
|
353
|
-
new {
|
|
354
|
-
Id = Guid.Parse("$PERM_SECTION_EXECUTE_GUID"),
|
|
355
|
-
Path = "$APP.$MODULE.$SECTION.execute",
|
|
356
|
-
Level = PermissionLevel.Section,
|
|
357
|
-
Action = PermissionAction.Execute,
|
|
358
|
-
IsWildcard = false,
|
|
359
|
-
SectionId = {section}SectionId,
|
|
360
|
-
Description = "Execute actions in $SECTION_LABEL",
|
|
361
|
-
CreatedAt = seedDate
|
|
362
|
-
}
|
|
363
|
-
```
|
|
364
|
-
|
|
365
|
-
---
|
|
366
|
-
|
|
367
|
-
## TEMPLATE: RESOURCE PERMISSIONS SEED (Level 4)
|
|
368
|
-
|
|
369
|
-
> **Usage:** Finest level - sub-resources with distinct permissions (e.g.: Prompts → Blocks)
|
|
370
|
-
|
|
371
|
-
### Permission Entity - Resource Level
|
|
372
|
-
|
|
373
|
-
```csharp
|
|
374
|
-
// In PermissionConfiguration.cs - HasData()
|
|
375
|
-
// Pattern: {application}.{module}.{section}.{resource}.{action}
|
|
376
|
-
|
|
377
|
-
// Declare ResourceId (must match NavigationResourceConfiguration.cs)
|
|
378
|
-
var {resource}ResourceId = Guid.Parse("$RESOURCE_GUID");
|
|
379
|
-
|
|
380
|
-
// Wildcard Resource
|
|
381
|
-
new {
|
|
382
|
-
Id = Guid.Parse("$PERM_RESOURCE_WILDCARD_GUID"),
|
|
383
|
-
Path = "$APP.$MODULE.$SECTION.$RESOURCE.*",
|
|
384
|
-
Level = PermissionLevel.Resource,
|
|
385
|
-
IsWildcard = true,
|
|
386
|
-
ResourceId = {resource}ResourceId,
|
|
387
|
-
Description = "Full $RESOURCE_LABEL access",
|
|
388
|
-
CreatedAt = seedDate
|
|
389
|
-
},
|
|
390
|
-
// Read
|
|
391
|
-
new {
|
|
392
|
-
Id = Guid.Parse("$PERM_RESOURCE_READ_GUID"),
|
|
393
|
-
Path = "$APP.$MODULE.$SECTION.$RESOURCE.read",
|
|
394
|
-
Level = PermissionLevel.Resource,
|
|
395
|
-
Action = PermissionAction.Read,
|
|
396
|
-
IsWildcard = false,
|
|
397
|
-
ResourceId = {resource}ResourceId,
|
|
398
|
-
Description = "View $RESOURCE_LABEL",
|
|
399
|
-
CreatedAt = seedDate
|
|
400
|
-
},
|
|
401
|
-
// Create
|
|
402
|
-
new {
|
|
403
|
-
Id = Guid.Parse("$PERM_RESOURCE_CREATE_GUID"),
|
|
404
|
-
Path = "$APP.$MODULE.$SECTION.$RESOURCE.create",
|
|
405
|
-
Level = PermissionLevel.Resource,
|
|
406
|
-
Action = PermissionAction.Create,
|
|
407
|
-
IsWildcard = false,
|
|
408
|
-
ResourceId = {resource}ResourceId,
|
|
409
|
-
Description = "Create $RESOURCE_LABEL",
|
|
410
|
-
CreatedAt = seedDate
|
|
411
|
-
},
|
|
412
|
-
// Update
|
|
413
|
-
new {
|
|
414
|
-
Id = Guid.Parse("$PERM_RESOURCE_UPDATE_GUID"),
|
|
415
|
-
Path = "$APP.$MODULE.$SECTION.$RESOURCE.update",
|
|
416
|
-
Level = PermissionLevel.Resource,
|
|
417
|
-
Action = PermissionAction.Update,
|
|
418
|
-
IsWildcard = false,
|
|
419
|
-
ResourceId = {resource}ResourceId,
|
|
420
|
-
Description = "Update $RESOURCE_LABEL",
|
|
421
|
-
CreatedAt = seedDate
|
|
422
|
-
},
|
|
423
|
-
// Delete
|
|
424
|
-
new {
|
|
425
|
-
Id = Guid.Parse("$PERM_RESOURCE_DELETE_GUID"),
|
|
426
|
-
Path = "$APP.$MODULE.$SECTION.$RESOURCE.delete",
|
|
427
|
-
Level = PermissionLevel.Resource,
|
|
428
|
-
Action = PermissionAction.Delete,
|
|
429
|
-
IsWildcard = false,
|
|
430
|
-
ResourceId = {resource}ResourceId,
|
|
431
|
-
Description = "Delete $RESOURCE_LABEL",
|
|
432
|
-
CreatedAt = seedDate
|
|
433
|
-
}
|
|
434
|
-
```
|
|
435
|
-
|
|
436
|
-
---
|
|
437
|
-
|
|
438
|
-
## TEMPLATE: BULK OPERATIONS PERMISSIONS
|
|
439
|
-
|
|
440
|
-
> **MANDATORY:** Always provide for CRUD modules
|
|
441
|
-
|
|
442
|
-
### Permission Entity - Bulk Operations
|
|
443
|
-
|
|
444
|
-
```csharp
|
|
445
|
-
// In PermissionConfiguration.cs - HasData()
|
|
446
|
-
// Add after standard CRUD permissions
|
|
447
|
-
|
|
448
|
-
// Bulk Create
|
|
449
|
-
new {
|
|
450
|
-
Id = Guid.Parse("$PERM_BULK_CREATE_GUID"),
|
|
451
|
-
Path = "$APP.$MODULE.bulk-create",
|
|
452
|
-
Level = PermissionLevel.Module,
|
|
453
|
-
Action = PermissionAction.Create,
|
|
454
|
-
IsWildcard = false,
|
|
455
|
-
ModuleId = {module}ModuleId,
|
|
456
|
-
Description = "Bulk create $MODULE_LABEL",
|
|
457
|
-
CreatedAt = seedDate
|
|
458
|
-
},
|
|
459
|
-
// Bulk Update
|
|
460
|
-
new {
|
|
461
|
-
Id = Guid.Parse("$PERM_BULK_UPDATE_GUID"),
|
|
462
|
-
Path = "$APP.$MODULE.bulk-update",
|
|
463
|
-
Level = PermissionLevel.Module,
|
|
464
|
-
Action = PermissionAction.Update,
|
|
465
|
-
IsWildcard = false,
|
|
466
|
-
ModuleId = {module}ModuleId,
|
|
467
|
-
Description = "Bulk update $MODULE_LABEL",
|
|
468
|
-
CreatedAt = seedDate
|
|
469
|
-
},
|
|
470
|
-
// Bulk Delete
|
|
471
|
-
new {
|
|
472
|
-
Id = Guid.Parse("$PERM_BULK_DELETE_GUID"),
|
|
473
|
-
Path = "$APP.$MODULE.bulk-delete",
|
|
474
|
-
Level = PermissionLevel.Module,
|
|
475
|
-
Action = PermissionAction.Delete,
|
|
476
|
-
IsWildcard = false,
|
|
477
|
-
ModuleId = {module}ModuleId,
|
|
478
|
-
Description = "Bulk delete $MODULE_LABEL",
|
|
479
|
-
CreatedAt = seedDate
|
|
480
|
-
},
|
|
481
|
-
// Export
|
|
482
|
-
new {
|
|
483
|
-
Id = Guid.Parse("$PERM_EXPORT_GUID"),
|
|
484
|
-
Path = "$APP.$MODULE.export",
|
|
485
|
-
Level = PermissionLevel.Module,
|
|
486
|
-
Action = PermissionAction.Execute,
|
|
487
|
-
IsWildcard = false,
|
|
488
|
-
ModuleId = {module}ModuleId,
|
|
489
|
-
Description = "Export $MODULE_LABEL data",
|
|
490
|
-
CreatedAt = seedDate
|
|
491
|
-
},
|
|
492
|
-
// Import
|
|
493
|
-
new {
|
|
494
|
-
Id = Guid.Parse("$PERM_IMPORT_GUID"),
|
|
495
|
-
Path = "$APP.$MODULE.import",
|
|
496
|
-
Level = PermissionLevel.Module,
|
|
497
|
-
Action = PermissionAction.Create,
|
|
498
|
-
IsWildcard = false,
|
|
499
|
-
ModuleId = {module}ModuleId,
|
|
500
|
-
Description = "Import $MODULE_LABEL data",
|
|
501
|
-
CreatedAt = seedDate
|
|
502
|
-
}
|
|
503
|
-
```
|
|
504
|
-
|
|
505
|
-
---
|
|
506
|
-
|
|
507
|
-
## COMPLETE EXAMPLE: "Products" Module in "Sales"
|
|
508
|
-
|
|
509
|
-
### Variables
|
|
510
|
-
|
|
511
|
-
```
|
|
512
|
-
$APP = sales
|
|
513
|
-
$APP_GUID = e2e2e2e2-2222-2222-2222-222222222222
|
|
514
|
-
$MODULE = products
|
|
515
|
-
$MODULE_GUID = e3e3e3e3-3333-3333-3333-333333333333
|
|
516
|
-
|
|
517
|
-
$LABEL_FR = Produits
|
|
518
|
-
$LABEL_EN = Products
|
|
519
|
-
$LABEL_IT = Prodotti
|
|
520
|
-
$LABEL_DE = Produkte
|
|
521
|
-
|
|
522
|
-
$DESC_FR = Gestion des produits
|
|
523
|
-
$DESC_EN = Product management
|
|
524
|
-
$DESC_IT = Gestione prodotti
|
|
525
|
-
$DESC_DE = Produktverwaltung
|
|
526
|
-
|
|
527
|
-
$ICON = Package
|
|
528
|
-
$ORDER = 1
|
|
529
|
-
```
|
|
530
|
-
|
|
531
|
-
### Files to modify
|
|
532
|
-
|
|
533
|
-
1. `NavigationApplicationConfiguration.cs` - Add Sales application
|
|
534
|
-
2. `NavigationModuleConfiguration.cs` - Add Products module
|
|
535
|
-
3. `NavigationTranslationConfiguration.cs` - Add 4 translations × number of entities
|
|
536
|
-
4. `PermissionConfiguration.cs` - Add CRUD permissions
|
|
537
|
-
5. `RolePermissionConfiguration.cs` - Assign to default roles
|
|
538
|
-
|
|
539
|
-
---
|
|
540
|
-
|
|
541
|
-
## TEMPLATE: ENTITY SEED DATA (SeedData Provider)
|
|
542
|
-
|
|
543
|
-
> **Usage:** Runtime seed data for domain entities (Products, Orders, Tickets, etc.)
|
|
544
|
-
> **When:** User wants initial data for development/testing
|
|
545
|
-
> **Mechanism:** Loaded at startup by DevDataSeeder, NOT via EF Core HasData/migrations
|
|
546
|
-
> **Architecture:** Identical in core AND extensions
|
|
547
|
-
|
|
548
|
-
### Two Seeding Mechanisms
|
|
549
|
-
|
|
550
|
-
| Mechanism | When to Use | Applied Via |
|
|
551
|
-
|-----------|-------------|-------------|
|
|
552
|
-
| **HasData()** in Configuration.cs | System entities (Navigation, Permissions, Roles) | EF Core migrations |
|
|
553
|
-
| **SeedData.cs + DevDataSeeder** | Domain entities (Tickets, Products, Orders) | Application startup |
|
|
554
|
-
|
|
555
|
-
**Rule:** Navigation, Permission, and RolePermission seeds use HasData().
|
|
556
|
-
Domain entity seeds use the SeedData provider pattern below.
|
|
557
|
-
|
|
558
|
-
### SeedData Class Pattern
|
|
559
|
-
|
|
560
|
-
```csharp
|
|
561
|
-
// Infrastructure/Persistence/Seeding/Data/{Domain}/{EntityName}SeedData.cs
|
|
562
|
-
|
|
563
|
-
using SmartStack.Domain.{Application}.{Module};
|
|
564
|
-
|
|
565
|
-
namespace SmartStack.Infrastructure.Persistence.Seeding.Data.{Domain};
|
|
566
|
-
|
|
567
|
-
/// <summary>
|
|
568
|
-
/// Demo {EntityName} data for development and testing.
|
|
569
|
-
/// </summary>
|
|
570
|
-
public static class {EntityName}SeedData
|
|
571
|
-
{
|
|
572
|
-
// ============================================================
|
|
573
|
-
// DETERMINISTIC IDs - for referencing in other seed data
|
|
574
|
-
// ============================================================
|
|
575
|
-
|
|
576
|
-
public static readonly Guid Sample1Id = Guid.Parse("$SEED_GUID_1");
|
|
577
|
-
public static readonly Guid Sample2Id = Guid.Parse("$SEED_GUID_2");
|
|
578
|
-
public static readonly Guid Sample3Id = Guid.Parse("$SEED_GUID_3");
|
|
579
|
-
|
|
580
|
-
/// <summary>
|
|
581
|
-
/// Returns all demo {EntityName} entities.
|
|
582
|
-
/// </summary>
|
|
583
|
-
internal static IEnumerable<{EntityName}SeedItem> GetAll{EntityName}s()
|
|
584
|
-
{
|
|
585
|
-
return new[]
|
|
586
|
-
{
|
|
587
|
-
new {EntityName}SeedItem
|
|
588
|
-
{
|
|
589
|
-
Id = Sample1Id,
|
|
590
|
-
// ... entity properties with realistic sample data
|
|
591
|
-
CreatedByUserId = UserSeedData.SystemUserId
|
|
592
|
-
},
|
|
593
|
-
new {EntityName}SeedItem
|
|
594
|
-
{
|
|
595
|
-
Id = Sample2Id,
|
|
596
|
-
// ... varied data
|
|
597
|
-
CreatedByUserId = UserSeedData.SystemUserId
|
|
598
|
-
},
|
|
599
|
-
new {EntityName}SeedItem
|
|
600
|
-
{
|
|
601
|
-
Id = Sample3Id,
|
|
602
|
-
// ... varied data
|
|
603
|
-
CreatedByUserId = UserSeedData.SystemUserId
|
|
604
|
-
}
|
|
605
|
-
};
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
/// <summary>
|
|
610
|
-
/// Seed item DTO for {EntityName} (avoids domain factory methods during seeding).
|
|
611
|
-
/// </summary>
|
|
612
|
-
internal class {EntityName}SeedItem
|
|
613
|
-
{
|
|
614
|
-
public Guid Id { get; init; }
|
|
615
|
-
// ... all required properties matching the domain entity
|
|
616
|
-
public Guid CreatedByUserId { get; init; }
|
|
617
|
-
}
|
|
618
|
-
```
|
|
619
|
-
|
|
620
|
-
### Multi-Tenant Entity Seed Pattern
|
|
621
|
-
|
|
622
|
-
For entities belonging to a tenant, organize by tenant:
|
|
623
|
-
|
|
624
|
-
```csharp
|
|
625
|
-
internal static IEnumerable<{EntityName}SeedItem> GetAll{EntityName}s()
|
|
626
|
-
{
|
|
627
|
-
var items = new List<{EntityName}SeedItem>();
|
|
628
|
-
|
|
629
|
-
items.AddRange(GetTenant1{EntityName}s());
|
|
630
|
-
items.AddRange(GetTenant2{EntityName}s());
|
|
631
|
-
|
|
632
|
-
return items;
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
private static IEnumerable<{EntityName}SeedItem> GetTenant1{EntityName}s()
|
|
636
|
-
{
|
|
637
|
-
var tenantId = TenantSeedData.AcmeCorporationId;
|
|
638
|
-
var userId = TenantMembershipSeedData.AcmeOwnerId;
|
|
639
|
-
|
|
640
|
-
return new[]
|
|
641
|
-
{
|
|
642
|
-
new {EntityName}SeedItem
|
|
643
|
-
{
|
|
644
|
-
Id = Guid.Parse("$GUID"),
|
|
645
|
-
TenantId = tenantId,
|
|
646
|
-
CreatedByUserId = userId,
|
|
647
|
-
// ... properties
|
|
648
|
-
}
|
|
649
|
-
};
|
|
650
|
-
}
|
|
651
|
-
```
|
|
652
|
-
|
|
653
|
-
### DevDataSeeder Registration Pattern
|
|
654
|
-
|
|
655
|
-
```csharp
|
|
656
|
-
// In Infrastructure/Persistence/Seeding/DevDataSeeder.cs
|
|
657
|
-
|
|
658
|
-
// 1. Add using statement
|
|
659
|
-
using SmartStack.Infrastructure.Persistence.Seeding.Data.{Domain};
|
|
660
|
-
|
|
661
|
-
// 2. Add call in SeedAsync() method (after existing seed calls)
|
|
662
|
-
public async Task SeedAsync(CancellationToken cancellationToken = default)
|
|
663
|
-
{
|
|
664
|
-
// ... existing seed calls ...
|
|
665
|
-
|
|
666
|
-
await Seed{EntityName}sAsync(cancellationToken); // <-- ADD THIS
|
|
667
|
-
|
|
668
|
-
// ... summary logging ...
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
// 3. Add private seeding method
|
|
672
|
-
private async Task Seed{EntityName}sAsync(CancellationToken cancellationToken)
|
|
673
|
-
{
|
|
674
|
-
_logger.LogInformation("Seeding demo {EntityName}s...");
|
|
675
|
-
|
|
676
|
-
var existingCount = await _context.{EntityName}s.CountAsync(cancellationToken);
|
|
677
|
-
if (existingCount > 0)
|
|
678
|
-
{
|
|
679
|
-
_logger.LogWarning("{EntityName}s already seeded ({Count} exist), skipping.", existingCount);
|
|
680
|
-
return;
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
var createdCount = 0;
|
|
684
|
-
|
|
685
|
-
foreach (var seedItem in {EntityName}SeedData.GetAll{EntityName}s())
|
|
686
|
-
{
|
|
687
|
-
var entity = {EntityName}.Create(
|
|
688
|
-
// Map seedItem properties to factory method parameters
|
|
689
|
-
);
|
|
690
|
-
|
|
691
|
-
// Set deterministic ID (factory generates random GUID)
|
|
692
|
-
typeof({EntityName}).GetProperty("Id")?.SetValue(entity, seedItem.Id);
|
|
693
|
-
|
|
694
|
-
_context.{EntityName}s.Add(entity);
|
|
695
|
-
createdCount++;
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
if (createdCount > 0)
|
|
699
|
-
{
|
|
700
|
-
await _context.SaveChangesAsync(cancellationToken);
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
_logger.LogInformation("Created {Count} demo {EntityName}s.", createdCount);
|
|
704
|
-
}
|
|
705
|
-
```
|
|
706
|
-
|
|
707
|
-
### Seed Data GUID Generation
|
|
708
|
-
|
|
709
|
-
```csharp
|
|
710
|
-
// Option 1: Hardcoded descriptive GUIDs (preferred for small sets)
|
|
711
|
-
public static readonly Guid ProductAlphaId = Guid.Parse("aa000001-0000-0000-0000-000000000001");
|
|
712
|
-
public static readonly Guid ProductBetaId = Guid.Parse("aa000001-0000-0000-0000-000000000002");
|
|
713
|
-
|
|
714
|
-
// Option 2: SHA256-based for larger sets
|
|
715
|
-
private static Guid GenerateEntityGuid(string uniqueKey)
|
|
716
|
-
{
|
|
717
|
-
using var sha256 = System.Security.Cryptography.SHA256.Create();
|
|
718
|
-
var hash = sha256.ComputeHash(System.Text.Encoding.UTF8.GetBytes($"{EntityName}-{uniqueKey}"));
|
|
719
|
-
return new Guid(hash.Take(16).ToArray());
|
|
720
|
-
}
|
|
721
|
-
```
|
|
722
|
-
|
|
723
|
-
### Best Practices
|
|
724
|
-
|
|
725
|
-
| Practice | Description |
|
|
726
|
-
|----------|-------------|
|
|
727
|
-
| Deterministic IDs | NEVER use `Guid.NewGuid()` - seeds must be idempotent |
|
|
728
|
-
| Idempotent | Always check `if exists` before creating |
|
|
729
|
-
| Use factory methods | Call `Entity.Create(...)` not `new Entity()` |
|
|
730
|
-
| Set ID via reflection | Factory methods generate random IDs; override after creation |
|
|
731
|
-
| Realistic data | Use plausible names, descriptions, amounts |
|
|
732
|
-
| Cover all states | Include entities in different statuses (active, closed, etc.) |
|
|
733
|
-
| Reference existing seeds | Use IDs from TenantSeedData, UserSeedData for foreign keys |
|
|
734
|
-
| SaveChanges per batch | Call SaveChanges after each entity group |
|
|
735
|
-
|
|
736
|
-
---
|
|
737
|
-
|
|
738
|
-
## TEMPLATE: CLIENT SEED DATA PROVIDER
|
|
739
|
-
|
|
740
|
-
> **Usage:** Client projects (projectType: "client") to seed data into the Core schema
|
|
741
|
-
> **Mechanism:** Runtime seeding via IClientSeedDataProvider (no Core migrations required)
|
|
742
|
-
> **When:** Generated automatically at step 03b if the project is of type client
|
|
743
|
-
|
|
744
|
-
### Difference with HasData()
|
|
745
|
-
|
|
746
|
-
| Aspect | HasData() (core) | IClientSeedDataProvider (client) |
|
|
747
|
-
|--------|-----------------|----------------------------------|
|
|
748
|
-
| When | EF Core migration | Application startup |
|
|
749
|
-
| Where | In Core migrations | Runtime via CoreDbContext |
|
|
750
|
-
| Who | SmartStack.app | Client project |
|
|
751
|
-
| Idempotent | Yes (single migration) | Yes (check existence) |
|
|
752
|
-
| Core migration | Yes | No |
|
|
753
|
-
| Entities created with | Anonymous objects | Factory methods (mandatory) |
|
|
754
|
-
|
|
755
|
-
### Implementation Pattern
|
|
756
|
-
|
|
757
|
-
The provider is generated in:
|
|
758
|
-
`Infrastructure/Persistence/Seeding/{AppPascalName}SeedDataProvider.cs`
|
|
759
|
-
|
|
760
|
-
It consumes the SeedData files generated in steps 01-03:
|
|
761
|
-
- `{Module}NavigationSeedData.cs` (GUIDs + navigation data)
|
|
762
|
-
- `{Module}NavigationTranslationSeedData.cs` (4-language translations)
|
|
763
|
-
- `{Module}PermissionSeedData.cs` (RBAC permissions)
|
|
764
|
-
- `{Module}RolePermissionSeedData.cs` (role -> permission mappings)
|
|
765
|
-
|
|
766
|
-
### DI Registration
|
|
767
|
-
|
|
768
|
-
In `Infrastructure/DependencyInjection.cs`:
|
|
769
|
-
```csharp
|
|
770
|
-
services.AddScoped<IClientSeedDataProvider, {AppPascalName}SeedDataProvider>();
|
|
771
|
-
```
|
|
772
|
-
|
|
773
|
-
### Execution Pipeline
|
|
774
|
-
|
|
775
|
-
```
|
|
776
|
-
InitializeSmartStackAsync()
|
|
777
|
-
1. MigrateAsync() -> Core schema created
|
|
778
|
-
2. IDatabaseSeeder.SeedAsync() -> System users, tenant
|
|
779
|
-
3. IClientSeedDataProvider.Seed*() -> Navigation + Roles + Permissions + RolePermissions CLIENT
|
|
780
|
-
4. IDevDataSeeder.SeedAsync() -> Dev data
|
|
781
|
-
5. InitializeNavigationRoutingAsync -> Routes loaded (including client routes)
|
|
782
|
-
```
|
|
783
|
-
|
|
784
|
-
### Provider Template
|
|
785
|
-
|
|
786
|
-
```csharp
|
|
787
|
-
using Microsoft.EntityFrameworkCore;
|
|
788
|
-
using SmartStack.Application.Common.Interfaces;
|
|
789
|
-
using SmartStack.Domain.Navigation;
|
|
790
|
-
using SmartStack.Domain.Administration.Roles;
|
|
791
|
-
|
|
792
|
-
namespace {BaseNamespace}.Infrastructure.Persistence.Seeding;
|
|
793
|
-
|
|
794
|
-
public class {AppPascalName}SeedDataProvider : IClientSeedDataProvider
|
|
795
|
-
{
|
|
796
|
-
public int Order => 100;
|
|
797
|
-
|
|
798
|
-
public async Task SeedNavigationAsync(ICoreDbContext context, CancellationToken ct)
|
|
799
|
-
{
|
|
800
|
-
// NOTE: Idempotence is at MODULE level (not application level).
|
|
801
|
-
// If the application already exists, we reuse it and seed any missing modules.
|
|
802
|
-
// This allows adding Module 2+ to an existing application.
|
|
803
|
-
var existingApp = await context.NavigationApplications
|
|
804
|
-
.FirstOrDefaultAsync(a => a.Code == "{app_code}", ct);
|
|
805
|
-
|
|
806
|
-
NavigationApplication app;
|
|
807
|
-
if (existingApp != null)
|
|
808
|
-
{
|
|
809
|
-
app = existingApp; // Application already seeded — reuse for module seeding below
|
|
810
|
-
}
|
|
811
|
-
else
|
|
812
|
-
{
|
|
813
|
-
app = NavigationApplication.Create(
|
|
814
|
-
"{app_code}", "{app_label_en}",
|
|
815
|
-
"{app_desc_en}", "{app_icon}", IconType.Lucide,
|
|
816
|
-
"/{app_code}", {display_order});
|
|
817
|
-
context.NavigationApplications.Add(app);
|
|
818
|
-
await ((DbContext)context).SaveChangesAsync(ct);
|
|
819
|
-
|
|
820
|
-
// Application translations (only when creating new application)
|
|
821
|
-
// foreach (var t in NavigationApplicationSeedData.GetTranslationEntries()) { ... }
|
|
822
|
-
await ((DbContext)context).SaveChangesAsync(ct);
|
|
823
|
-
}
|
|
824
|
-
|
|
825
|
-
// Create modules (idempotent per-module — check each before inserting)
|
|
826
|
-
// var mod1Exists = await context.NavigationModules.AnyAsync(m => m.Code == "{module_code}" && m.ApplicationId == app.Id, ct);
|
|
827
|
-
// if (!mod1Exists) { var mod1 = NavigationModule.Create(...); context.NavigationModules.Add(mod1); }
|
|
828
|
-
|
|
829
|
-
// Module translations — IDEMPOTENT (unique index IX_nav_Translations_EntityType_EntityId_LanguageCode)
|
|
830
|
-
// CRITICAL: Always check before inserting to avoid duplicate key errors on re-runs
|
|
831
|
-
// if (!await context.NavigationTranslations.AnyAsync(
|
|
832
|
-
// t => t.EntityId == {Module}NavigationSeedData.{Module}ModuleId && t.EntityType == NavigationEntityType.Module, ct))
|
|
833
|
-
// { foreach (var t in {Module}NavigationSeedData.GetTranslationEntries()) { ... } }
|
|
834
|
-
|
|
835
|
-
// Sections (idempotent per-section — check each before inserting)
|
|
836
|
-
// var secExists = await context.NavigationSections.AnyAsync(s => s.Code == secEntry.Code && s.ModuleId == ..., ct);
|
|
837
|
-
// Use NavigationSection.Create(moduleId, code, label, description, icon, iconType, route, displayOrder)
|
|
838
|
-
|
|
839
|
-
// Section translations — IDEMPOTENT (same guard pattern as module translations)
|
|
840
|
-
// if (!await context.NavigationTranslations.AnyAsync(
|
|
841
|
-
// t => t.EntityId == secEntry.Id && t.EntityType == NavigationEntityType.Section, ct))
|
|
842
|
-
|
|
843
|
-
// Resources (idempotent per-resource — check each before inserting)
|
|
844
|
-
// var resExists = await context.NavigationResources.AnyAsync(r => r.Code == resEntry.Code && r.SectionId == ..., ct);
|
|
845
|
-
// Use NavigationResource.Create(sectionId, code, label, entityType, route, displayOrder)
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
public async Task SeedRolesAsync(ICoreDbContext context, CancellationToken ct)
|
|
849
|
-
{
|
|
850
|
-
// Check idempotence
|
|
851
|
-
var exists = await context.Roles
|
|
852
|
-
.AnyAsync(r => r.ApplicationId == ApplicationRolesSeedData.ApplicationId, ct);
|
|
853
|
-
if (exists) return;
|
|
854
|
-
|
|
855
|
-
// Create application-scoped roles (Admin, Manager, Contributor, Viewer)
|
|
856
|
-
foreach (var entry in ApplicationRolesSeedData.GetRoleEntries())
|
|
857
|
-
{
|
|
858
|
-
var role = Role.Create(
|
|
859
|
-
entry.Code,
|
|
860
|
-
entry.Name,
|
|
861
|
-
entry.Description,
|
|
862
|
-
entry.ApplicationId,
|
|
863
|
-
entry.IsSystem);
|
|
864
|
-
context.Roles.Add(role);
|
|
865
|
-
}
|
|
866
|
-
await ((DbContext)context).SaveChangesAsync(ct);
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
public async Task SeedPermissionsAsync(ICoreDbContext context, CancellationToken ct)
|
|
870
|
-
{
|
|
871
|
-
var exists = await context.Permissions
|
|
872
|
-
.AnyAsync(p => p.Path == "{full_path}.*", ct);
|
|
873
|
-
if (exists) return;
|
|
874
|
-
|
|
875
|
-
// Create permissions from {Module}PermissionSeedData
|
|
876
|
-
// Use Permission.CreateForModule(...) and Permission.CreateWildcard(...)
|
|
877
|
-
}
|
|
878
|
-
|
|
879
|
-
public async Task SeedRolePermissionsAsync(ICoreDbContext context, CancellationToken ct)
|
|
880
|
-
{
|
|
881
|
-
var exists = await context.RolePermissions
|
|
882
|
-
.AnyAsync(rp => rp.Permission!.Path.StartsWith("{full_path}."), ct);
|
|
883
|
-
if (exists) return;
|
|
884
|
-
|
|
885
|
-
// Create role-permissions from {Module}RolePermissionSeedData
|
|
886
|
-
// Use RolePermission.Create(roleId, permissionId, "system")
|
|
887
|
-
}
|
|
888
|
-
}
|
|
889
|
-
```
|
|
890
|
-
|
|
891
|
-
### Critical Rules
|
|
892
|
-
|
|
893
|
-
| Rule | Description |
|
|
894
|
-
|------|-------------|
|
|
895
|
-
| Factory methods | `NavigationModule.Create(...)`, `NavigationSection.Create(...)`, `NavigationResource.Create(...)`, `Permission.CreateForModule(...)` - NEVER `new Entity()` |
|
|
896
|
-
| Idempotence | Each Seed method checks existence before inserting |
|
|
897
|
-
| SaveChanges per group | Navigation -> save -> Roles -> save -> Permissions -> save -> RolePermissions -> save |
|
|
898
|
-
| Deterministic GUIDs | Use IDs from SeedData classes (not `Guid.NewGuid()`) |
|
|
899
|
-
| FK resolution by Code | Parent modules found by `Code`, not hardcoded GUID |
|
|
900
|
-
| Section/Resource conditionality | Only generate if `seedDataCore.navigationSections` / `seedDataCore.navigationResources` exist in feature.json |
|
|
901
|
-
|
|
902
|
-
---
|
|
903
|
-
|
|
904
|
-
## SEED CHECKLIST
|
|
905
|
-
|
|
906
|
-
| Check | Status |
|
|
907
|
-
|-------|--------|
|
|
908
|
-
| ☐ Deterministic GUID (not NewGuid) | |
|
|
909
|
-
| ☐ 4 languages for each navigation entity (modules, sections, resources) | |
|
|
910
|
-
| ☐ Index translations continue existing sequence | |
|
|
911
|
-
| ☐ Route aligned with permission path | |
|
|
912
|
-
| ☐ DisplayOrder consistent | |
|
|
913
|
-
| ☐ CRUD permissions created (Level 2 - Module) | |
|
|
914
|
-
| ☐ Section permissions if sub-pages (Level 3) | |
|
|
915
|
-
| ☐ NavigationSections seeded (if `seedDataCore.navigationSections` in feature.json) | |
|
|
916
|
-
| ☐ NavigationResources seeded (if `seedDataCore.navigationResources` in feature.json) | |
|
|
917
|
-
| ☐ Resource permissions if sub-resources (Level 4) | |
|
|
918
|
-
| ☐ Bulk Operations permissions created | |
|
|
919
|
-
| ☐ RolePermissions assigned | |
|
|
920
|
-
| ☐ Entity SeedData.cs created (if user opted in) | |
|
|
921
|
-
| ☐ DevDataSeeder.cs updated (if user opted in) | |
|
|
922
|
-
| ☐ IClientSeedDataProvider generated (client projects only) | |
|
|
923
|
-
| ☐ Provider registered in DI (client projects only) | |
|
|
924
|
-
|
|
925
|
-
---
|
|
926
|
-
|
|
927
|
-
## PERMISSIONS HIERARCHY (4 Levels)
|
|
928
|
-
|
|
929
|
-
```
|
|
930
|
-
Level 1: APPLICATION
|
|
931
|
-
└─ Path: {application}.*
|
|
932
|
-
└─ E.g.: administration.* → Full app access
|
|
933
|
-
|
|
934
|
-
Level 2: MODULE
|
|
935
|
-
└─ Path: {application}.{module}.{action}
|
|
936
|
-
└─ E.g.: administration.users.read
|
|
937
|
-
|
|
938
|
-
Level 3: SECTION
|
|
939
|
-
└─ Path: {application}.{module}.{section}.{action}
|
|
940
|
-
└─ E.g.: administration.ai.settings.update
|
|
941
|
-
|
|
942
|
-
Level 4: RESOURCE (finest granularity)
|
|
943
|
-
└─ Path: {application}.{module}.{section}.{resource}.{action}
|
|
944
|
-
└─ E.g.: administration.ai.prompts.blocks.delete
|
|
945
|
-
```
|
|
946
|
-
|
|
947
|
-
---
|
|
948
|
-
|
|
949
|
-
## SQL OBJECTS INFRASTRUCTURE
|
|
950
|
-
|
|
951
|
-
> **Usage:** SQL Server functions (TVFs, scalar), views, and stored procedures managed as embedded resources
|
|
952
|
-
> **Pattern:** Same as SmartStack.app — `CREATE OR ALTER` for idempotency, applied via migrations
|
|
953
|
-
> **Location:** `Infrastructure/Persistence/SqlObjects/`
|
|
954
|
-
|
|
955
|
-
### Directory Structure
|
|
956
|
-
|
|
957
|
-
```
|
|
958
|
-
Infrastructure/Persistence/SqlObjects/
|
|
959
|
-
├── SqlObjectHelper.cs ← Helper to apply SQL from embedded resources
|
|
960
|
-
├── Functions/ ← Table-Valued & Scalar functions
|
|
961
|
-
│ └── fn_{FunctionName}.sql ← CREATE OR ALTER FUNCTION [schema].[fn_Name]
|
|
962
|
-
├── Views/ ← Future: SQL views
|
|
963
|
-
│ └── vw_{ViewName}.sql
|
|
964
|
-
└── StoredProcedures/ ← Future: Stored procedures
|
|
965
|
-
└── sp_{ProcName}.sql
|
|
966
|
-
```
|
|
967
|
-
|
|
968
|
-
### SqlObjectHelper Pattern
|
|
969
|
-
|
|
970
|
-
```csharp
|
|
971
|
-
// Infrastructure/Persistence/SqlObjects/SqlObjectHelper.cs
|
|
972
|
-
|
|
973
|
-
using System.Reflection;
|
|
974
|
-
using Microsoft.EntityFrameworkCore.Migrations;
|
|
975
|
-
|
|
976
|
-
namespace {BaseNamespace}.Infrastructure.Persistence.SqlObjects;
|
|
977
|
-
|
|
978
|
-
/// <summary>
|
|
979
|
-
/// Helper for applying SQL objects from embedded resources.
|
|
980
|
-
/// SQL files use CREATE OR ALTER for idempotency — safe to re-run on any migration or squash.
|
|
981
|
-
/// </summary>
|
|
982
|
-
public static class SqlObjectHelper
|
|
983
|
-
{
|
|
984
|
-
private static readonly Assembly Assembly = typeof(SqlObjectHelper).Assembly;
|
|
985
|
-
private const string ResourcePrefix = "{BaseNamespace}.Infrastructure.Persistence.SqlObjects.";
|
|
986
|
-
|
|
987
|
-
/// <summary>
|
|
988
|
-
/// Applies all SQL objects (functions, views, stored procedures).
|
|
989
|
-
/// Call in Up() method of a migration, especially after a squash.
|
|
990
|
-
/// </summary>
|
|
991
|
-
public static void ApplyAll(MigrationBuilder migrationBuilder)
|
|
992
|
-
{
|
|
993
|
-
ApplyAllFunctions(migrationBuilder);
|
|
994
|
-
}
|
|
995
|
-
|
|
996
|
-
public static void ApplyAllFunctions(MigrationBuilder migrationBuilder)
|
|
997
|
-
{
|
|
998
|
-
ApplyCategory(migrationBuilder, "Functions");
|
|
999
|
-
}
|
|
1000
|
-
|
|
1001
|
-
public static void ApplyOne(MigrationBuilder migrationBuilder, string resourceName)
|
|
1002
|
-
{
|
|
1003
|
-
var sql = ReadSqlObject(resourceName);
|
|
1004
|
-
migrationBuilder.Sql(sql);
|
|
1005
|
-
}
|
|
1006
|
-
|
|
1007
|
-
public static string ReadSqlObject(string resourceName)
|
|
1008
|
-
{
|
|
1009
|
-
var fullName = ResourcePrefix + resourceName;
|
|
1010
|
-
using var stream = Assembly.GetManifestResourceStream(fullName)
|
|
1011
|
-
?? throw new InvalidOperationException(
|
|
1012
|
-
$"SQL object resource '{fullName}' not found. " +
|
|
1013
|
-
$"Ensure the .sql file is marked as EmbeddedResource in the .csproj. " +
|
|
1014
|
-
$"Available: {string.Join(", ", Assembly.GetManifestResourceNames().Where(n => n.EndsWith(".sql")))}");
|
|
1015
|
-
using var reader = new StreamReader(stream);
|
|
1016
|
-
return reader.ReadToEnd();
|
|
1017
|
-
}
|
|
1018
|
-
|
|
1019
|
-
private static void ApplyCategory(MigrationBuilder migrationBuilder, string category)
|
|
1020
|
-
{
|
|
1021
|
-
var prefix = ResourcePrefix + category + ".";
|
|
1022
|
-
var resources = Assembly.GetManifestResourceNames()
|
|
1023
|
-
.Where(n => n.StartsWith(prefix, StringComparison.Ordinal)
|
|
1024
|
-
&& n.EndsWith(".sql", StringComparison.Ordinal))
|
|
1025
|
-
.OrderBy(n => n, StringComparer.Ordinal);
|
|
1026
|
-
|
|
1027
|
-
foreach (var resource in resources)
|
|
1028
|
-
{
|
|
1029
|
-
using var stream = Assembly.GetManifestResourceStream(resource)!;
|
|
1030
|
-
using var reader = new StreamReader(stream);
|
|
1031
|
-
migrationBuilder.Sql(reader.ReadToEnd());
|
|
1032
|
-
}
|
|
1033
|
-
}
|
|
1034
|
-
}
|
|
1035
|
-
```
|
|
1036
|
-
|
|
1037
|
-
### .csproj Configuration (REQUIRED)
|
|
1038
|
-
|
|
1039
|
-
```xml
|
|
1040
|
-
<ItemGroup>
|
|
1041
|
-
<EmbeddedResource Include="Persistence\SqlObjects\**\*.sql" />
|
|
1042
|
-
</ItemGroup>
|
|
1043
|
-
```
|
|
1044
|
-
|
|
1045
|
-
### SQL File Convention
|
|
1046
|
-
|
|
1047
|
-
All `.sql` files MUST use `CREATE OR ALTER` for idempotency:
|
|
1048
|
-
|
|
1049
|
-
```sql
|
|
1050
|
-
-- Infrastructure/Persistence/SqlObjects/Functions/fn_GetUserGroupHierarchy.sql
|
|
1051
|
-
CREATE OR ALTER FUNCTION [extensions].[fn_GetEntityHierarchy](@EntityId UNIQUEIDENTIFIER)
|
|
1052
|
-
RETURNS TABLE
|
|
1053
|
-
AS
|
|
1054
|
-
RETURN
|
|
1055
|
-
(
|
|
1056
|
-
WITH Hierarchy AS (
|
|
1057
|
-
SELECT Id, ParentId, 0 AS Level
|
|
1058
|
-
FROM [extensions].[fb_Entities]
|
|
1059
|
-
WHERE Id = @EntityId
|
|
1060
|
-
UNION ALL
|
|
1061
|
-
SELECT child.Id, child.ParentId, parent.Level + 1
|
|
1062
|
-
FROM [extensions].[fb_Entities] child
|
|
1063
|
-
INNER JOIN Hierarchy parent ON child.ParentId = parent.Id
|
|
1064
|
-
WHERE parent.Level < 10
|
|
1065
|
-
)
|
|
1066
|
-
SELECT DISTINCT Id FROM Hierarchy
|
|
1067
|
-
);
|
|
1068
|
-
```
|
|
1069
|
-
|
|
1070
|
-
### Migration Integration
|
|
1071
|
-
|
|
1072
|
-
In a migration's `Up()` method, call SqlObjectHelper to apply/re-apply SQL objects:
|
|
1073
|
-
|
|
1074
|
-
```csharp
|
|
1075
|
-
protected override void Up(MigrationBuilder migrationBuilder)
|
|
1076
|
-
{
|
|
1077
|
-
// ... table creation, indexes, etc.
|
|
1078
|
-
|
|
1079
|
-
// Apply all SQL objects (idempotent — CREATE OR ALTER)
|
|
1080
|
-
SqlObjectHelper.ApplyAll(migrationBuilder);
|
|
1081
|
-
}
|
|
1082
|
-
```
|
|
1083
|
-
|
|
1084
|
-
Or apply a single function:
|
|
1085
|
-
```csharp
|
|
1086
|
-
SqlObjectHelper.ApplyOne(migrationBuilder, "Functions.fn_GetEntityHierarchy.sql");
|
|
1087
|
-
```
|
|
1088
|
-
|
|
1089
|
-
### When to Use SQL Objects
|
|
1090
|
-
|
|
1091
|
-
| Need | Solution |
|
|
1092
|
-
|------|----------|
|
|
1093
|
-
| Recursive hierarchy traversal | TVF with CTE (fn_GetXxxHierarchy) |
|
|
1094
|
-
| Complex aggregation for reports | SQL View (vw_XxxSummary) |
|
|
1095
|
-
| Bulk operations with business logic | Stored Procedure (sp_XxxBatch) |
|
|
1096
|
-
| Simple CRUD, filtering, sorting | EF Core LINQ (no SQL needed) |
|
|
1
|
+
# Templates DB Seed - Application Skill
|
|
2
|
+
|
|
3
|
+
> These templates generate EF Core seeds for navigation entities and their translations.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## GUID GENERATION RULES
|
|
8
|
+
|
|
9
|
+
```csharp
|
|
10
|
+
// NEVER generate sequential GUIDs with NewGuid()
|
|
11
|
+
// ALWAYS use the deterministic method
|
|
12
|
+
|
|
13
|
+
private static Guid GenerateGuid(int index)
|
|
14
|
+
{
|
|
15
|
+
// Format: 11111111-1111-1111-1111-{index:D12}
|
|
16
|
+
return Guid.Parse($"11111111-1111-1111-1111-{index:D12}");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// For existing seeds, continue the sequence
|
|
20
|
+
// Check the last index used in NavigationTranslationConfiguration.cs
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## TEMPLATE: APPLICATION
|
|
28
|
+
|
|
29
|
+
### Navigation Entity
|
|
30
|
+
|
|
31
|
+
```csharp
|
|
32
|
+
// In NavigationApplicationConfiguration.cs - HasData()
|
|
33
|
+
new {
|
|
34
|
+
Id = Guid.Parse("$APP_GUID"),
|
|
35
|
+
Code = "$CODE", // e.g.: "sales"
|
|
36
|
+
Label = "$LABEL_EN", // e.g.: "Sales"
|
|
37
|
+
Description = "$DESC_EN", // e.g.: "Sales management"
|
|
38
|
+
Icon = "$ICON", // e.g.: "TrendingUp"
|
|
39
|
+
IconType = IconType.Lucide,
|
|
40
|
+
Route = "/$CODE", // e.g.: "/sales"
|
|
41
|
+
DisplayOrder = $ORDER,
|
|
42
|
+
IsActive = true,
|
|
43
|
+
CreatedAt = seedDate
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Translations (4 languages)
|
|
48
|
+
|
|
49
|
+
```csharp
|
|
50
|
+
// Application translations
|
|
51
|
+
translations.Add(new {
|
|
52
|
+
Id = GenerateGuid(index++),
|
|
53
|
+
EntityType = NavigationEntityType.Application,
|
|
54
|
+
EntityId = $APP_GUID,
|
|
55
|
+
LanguageCode = "fr",
|
|
56
|
+
Label = "$LABEL_FR",
|
|
57
|
+
Description = "$DESC_FR",
|
|
58
|
+
CreatedAt = seedDate
|
|
59
|
+
});
|
|
60
|
+
translations.Add(new {
|
|
61
|
+
Id = GenerateGuid(index++),
|
|
62
|
+
EntityType = NavigationEntityType.Application,
|
|
63
|
+
EntityId = $APP_GUID,
|
|
64
|
+
LanguageCode = "en",
|
|
65
|
+
Label = "$LABEL_EN",
|
|
66
|
+
Description = "$DESC_EN",
|
|
67
|
+
CreatedAt = seedDate
|
|
68
|
+
});
|
|
69
|
+
translations.Add(new {
|
|
70
|
+
Id = GenerateGuid(index++),
|
|
71
|
+
EntityType = NavigationEntityType.Application,
|
|
72
|
+
EntityId = $APP_GUID,
|
|
73
|
+
LanguageCode = "it",
|
|
74
|
+
Label = "$LABEL_IT",
|
|
75
|
+
Description = "$DESC_IT",
|
|
76
|
+
CreatedAt = seedDate
|
|
77
|
+
});
|
|
78
|
+
translations.Add(new {
|
|
79
|
+
Id = GenerateGuid(index++),
|
|
80
|
+
EntityType = NavigationEntityType.Application,
|
|
81
|
+
EntityId = $APP_GUID,
|
|
82
|
+
LanguageCode = "de",
|
|
83
|
+
Label = "$LABEL_DE",
|
|
84
|
+
Description = "$DESC_DE",
|
|
85
|
+
CreatedAt = seedDate
|
|
86
|
+
});
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## TEMPLATE: MODULE
|
|
92
|
+
|
|
93
|
+
### Navigation Entity
|
|
94
|
+
|
|
95
|
+
```csharp
|
|
96
|
+
// In NavigationModuleConfiguration.cs - HasData()
|
|
97
|
+
new {
|
|
98
|
+
Id = Guid.Parse("$MODULE_GUID"),
|
|
99
|
+
ApplicationId = Guid.Parse("$APP_GUID"),
|
|
100
|
+
Code = "$CODE", // e.g.: "products"
|
|
101
|
+
Label = "$LABEL_EN", // e.g.: "Products"
|
|
102
|
+
Description = "$DESC_EN", // e.g.: "Product management"
|
|
103
|
+
Icon = "$ICON", // e.g.: "Package"
|
|
104
|
+
IconType = IconType.Lucide,
|
|
105
|
+
Route = "/$APP/$CODE", // e.g.: "/sales/products"
|
|
106
|
+
DisplayOrder = $ORDER,
|
|
107
|
+
IsActive = true,
|
|
108
|
+
CreatedAt = seedDate
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Translations (4 languages)
|
|
113
|
+
|
|
114
|
+
```csharp
|
|
115
|
+
// Module translations
|
|
116
|
+
translations.Add(new {
|
|
117
|
+
Id = GenerateGuid(index++),
|
|
118
|
+
EntityType = NavigationEntityType.Module,
|
|
119
|
+
EntityId = $MODULE_GUID,
|
|
120
|
+
LanguageCode = "fr",
|
|
121
|
+
Label = "$LABEL_FR",
|
|
122
|
+
Description = "$DESC_FR",
|
|
123
|
+
CreatedAt = seedDate
|
|
124
|
+
});
|
|
125
|
+
translations.Add(new {
|
|
126
|
+
Id = GenerateGuid(index++),
|
|
127
|
+
EntityType = NavigationEntityType.Module,
|
|
128
|
+
EntityId = $MODULE_GUID,
|
|
129
|
+
LanguageCode = "en",
|
|
130
|
+
Label = "$LABEL_EN",
|
|
131
|
+
Description = "$DESC_EN",
|
|
132
|
+
CreatedAt = seedDate
|
|
133
|
+
});
|
|
134
|
+
translations.Add(new {
|
|
135
|
+
Id = GenerateGuid(index++),
|
|
136
|
+
EntityType = NavigationEntityType.Module,
|
|
137
|
+
EntityId = $MODULE_GUID,
|
|
138
|
+
LanguageCode = "it",
|
|
139
|
+
Label = "$LABEL_IT",
|
|
140
|
+
Description = "$DESC_IT",
|
|
141
|
+
CreatedAt = seedDate
|
|
142
|
+
});
|
|
143
|
+
translations.Add(new {
|
|
144
|
+
Id = GenerateGuid(index++),
|
|
145
|
+
EntityType = NavigationEntityType.Module,
|
|
146
|
+
EntityId = $MODULE_GUID,
|
|
147
|
+
LanguageCode = "de",
|
|
148
|
+
Label = "$LABEL_DE",
|
|
149
|
+
Description = "$DESC_DE",
|
|
150
|
+
CreatedAt = seedDate
|
|
151
|
+
});
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## TEMPLATE: SECTION
|
|
157
|
+
|
|
158
|
+
### Navigation Entity
|
|
159
|
+
|
|
160
|
+
```csharp
|
|
161
|
+
// In NavigationSectionConfiguration.cs - HasData()
|
|
162
|
+
new {
|
|
163
|
+
Id = Guid.Parse("$SECTION_GUID"),
|
|
164
|
+
ModuleId = Guid.Parse("$MODULE_GUID"),
|
|
165
|
+
Code = "$CODE", // e.g.: "inventory"
|
|
166
|
+
Label = "$LABEL_EN", // e.g.: "Inventory"
|
|
167
|
+
Description = "$DESC_EN", // e.g.: "Inventory management"
|
|
168
|
+
Icon = "$ICON", // e.g.: "Warehouse"
|
|
169
|
+
IconType = IconType.Lucide,
|
|
170
|
+
Route = "/$APP/$MODULE/$CODE", // e.g.: "/sales/products/inventory"
|
|
171
|
+
DisplayOrder = $ORDER,
|
|
172
|
+
IsActive = true,
|
|
173
|
+
CreatedAt = seedDate
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Translations (4 languages)
|
|
178
|
+
|
|
179
|
+
```csharp
|
|
180
|
+
// Section translations
|
|
181
|
+
translations.Add(new {
|
|
182
|
+
Id = GenerateGuid(index++),
|
|
183
|
+
EntityType = NavigationEntityType.Section,
|
|
184
|
+
EntityId = $SECTION_GUID,
|
|
185
|
+
LanguageCode = "fr",
|
|
186
|
+
Label = "$LABEL_FR",
|
|
187
|
+
Description = "$DESC_FR",
|
|
188
|
+
CreatedAt = seedDate
|
|
189
|
+
});
|
|
190
|
+
translations.Add(new {
|
|
191
|
+
Id = GenerateGuid(index++),
|
|
192
|
+
EntityType = NavigationEntityType.Section,
|
|
193
|
+
EntityId = $SECTION_GUID,
|
|
194
|
+
LanguageCode = "en",
|
|
195
|
+
Label = "$LABEL_EN",
|
|
196
|
+
Description = "$DESC_EN",
|
|
197
|
+
CreatedAt = seedDate
|
|
198
|
+
});
|
|
199
|
+
translations.Add(new {
|
|
200
|
+
Id = GenerateGuid(index++),
|
|
201
|
+
EntityType = NavigationEntityType.Section,
|
|
202
|
+
EntityId = $SECTION_GUID,
|
|
203
|
+
LanguageCode = "it",
|
|
204
|
+
Label = "$LABEL_IT",
|
|
205
|
+
Description = "$DESC_IT",
|
|
206
|
+
CreatedAt = seedDate
|
|
207
|
+
});
|
|
208
|
+
translations.Add(new {
|
|
209
|
+
Id = GenerateGuid(index++),
|
|
210
|
+
EntityType = NavigationEntityType.Section,
|
|
211
|
+
EntityId = $SECTION_GUID,
|
|
212
|
+
LanguageCode = "de",
|
|
213
|
+
Label = "$LABEL_DE",
|
|
214
|
+
Description = "$DESC_DE",
|
|
215
|
+
CreatedAt = seedDate
|
|
216
|
+
});
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
## TEMPLATE: PERMISSIONS SEED
|
|
222
|
+
|
|
223
|
+
### Permission Entity
|
|
224
|
+
|
|
225
|
+
```csharp
|
|
226
|
+
// In PermissionConfiguration.cs - HasData()
|
|
227
|
+
// For each CRUD action + assign + execute
|
|
228
|
+
|
|
229
|
+
// Read
|
|
230
|
+
new {
|
|
231
|
+
Id = Guid.Parse("$PERM_READ_GUID"),
|
|
232
|
+
Code = "$APP.$MODULE.read",
|
|
233
|
+
Name = "View $MODULE_LABEL",
|
|
234
|
+
Description = "View $MODULE_LABEL list and details",
|
|
235
|
+
Category = "$APP.$MODULE",
|
|
236
|
+
CreatedAt = seedDate
|
|
237
|
+
},
|
|
238
|
+
// Create
|
|
239
|
+
new {
|
|
240
|
+
Id = Guid.Parse("$PERM_CREATE_GUID"),
|
|
241
|
+
Code = "$APP.$MODULE.create",
|
|
242
|
+
Name = "Create $MODULE_LABEL",
|
|
243
|
+
Description = "Create new $MODULE_LABEL",
|
|
244
|
+
Category = "$APP.$MODULE",
|
|
245
|
+
CreatedAt = seedDate
|
|
246
|
+
},
|
|
247
|
+
// Update
|
|
248
|
+
new {
|
|
249
|
+
Id = Guid.Parse("$PERM_UPDATE_GUID"),
|
|
250
|
+
Code = "$APP.$MODULE.update",
|
|
251
|
+
Name = "Update $MODULE_LABEL",
|
|
252
|
+
Description = "Modify existing $MODULE_LABEL",
|
|
253
|
+
Category = "$APP.$MODULE",
|
|
254
|
+
CreatedAt = seedDate
|
|
255
|
+
},
|
|
256
|
+
// Delete
|
|
257
|
+
new {
|
|
258
|
+
Id = Guid.Parse("$PERM_DELETE_GUID"),
|
|
259
|
+
Code = "$APP.$MODULE.delete",
|
|
260
|
+
Name = "Delete $MODULE_LABEL",
|
|
261
|
+
Description = "Remove $MODULE_LABEL",
|
|
262
|
+
Category = "$APP.$MODULE",
|
|
263
|
+
CreatedAt = seedDate
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### RolePermission Seed (Default Roles)
|
|
268
|
+
|
|
269
|
+
```csharp
|
|
270
|
+
// In RolePermissionConfiguration.cs - HasData()
|
|
271
|
+
// SuperAdmin gets all permissions automatically (wildcard)
|
|
272
|
+
|
|
273
|
+
// PlatformAdmin - if platform context
|
|
274
|
+
new { RoleId = Guid.Parse("...PlatformAdminRoleId..."), PermissionId = $PERM_READ_GUID },
|
|
275
|
+
new { RoleId = Guid.Parse("...PlatformAdminRoleId..."), PermissionId = $PERM_CREATE_GUID },
|
|
276
|
+
new { RoleId = Guid.Parse("...PlatformAdminRoleId..."), PermissionId = $PERM_UPDATE_GUID },
|
|
277
|
+
new { RoleId = Guid.Parse("...PlatformAdminRoleId..."), PermissionId = $PERM_DELETE_GUID },
|
|
278
|
+
|
|
279
|
+
// StandardUser - read only by default
|
|
280
|
+
new { RoleId = Guid.Parse("...StandardUserRoleId..."), PermissionId = $PERM_READ_GUID }
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
## TEMPLATE: SECTION PERMISSIONS SEED (Level 3)
|
|
286
|
+
|
|
287
|
+
> **Usage:** When a Module has sub-pages with different permissions
|
|
288
|
+
|
|
289
|
+
### Permission Entity - Section Level
|
|
290
|
+
|
|
291
|
+
```csharp
|
|
292
|
+
// In PermissionConfiguration.cs - HasData()
|
|
293
|
+
// Pattern: {application}.{module}.{section}.{action}
|
|
294
|
+
|
|
295
|
+
// Declare SectionId (must match NavigationSectionConfiguration.cs)
|
|
296
|
+
var {section}SectionId = Guid.Parse("$SECTION_GUID");
|
|
297
|
+
|
|
298
|
+
// Wildcard Section
|
|
299
|
+
new {
|
|
300
|
+
Id = Guid.Parse("$PERM_SECTION_WILDCARD_GUID"),
|
|
301
|
+
Path = "$APP.$MODULE.$SECTION.*",
|
|
302
|
+
Level = PermissionLevel.Section,
|
|
303
|
+
IsWildcard = true,
|
|
304
|
+
SectionId = {section}SectionId,
|
|
305
|
+
Description = "Full $SECTION_LABEL access",
|
|
306
|
+
CreatedAt = seedDate
|
|
307
|
+
},
|
|
308
|
+
// Read
|
|
309
|
+
new {
|
|
310
|
+
Id = Guid.Parse("$PERM_SECTION_READ_GUID"),
|
|
311
|
+
Path = "$APP.$MODULE.$SECTION.read",
|
|
312
|
+
Level = PermissionLevel.Section,
|
|
313
|
+
Action = PermissionAction.Read,
|
|
314
|
+
IsWildcard = false,
|
|
315
|
+
SectionId = {section}SectionId,
|
|
316
|
+
Description = "View $SECTION_LABEL",
|
|
317
|
+
CreatedAt = seedDate
|
|
318
|
+
},
|
|
319
|
+
// Create
|
|
320
|
+
new {
|
|
321
|
+
Id = Guid.Parse("$PERM_SECTION_CREATE_GUID"),
|
|
322
|
+
Path = "$APP.$MODULE.$SECTION.create",
|
|
323
|
+
Level = PermissionLevel.Section,
|
|
324
|
+
Action = PermissionAction.Create,
|
|
325
|
+
IsWildcard = false,
|
|
326
|
+
SectionId = {section}SectionId,
|
|
327
|
+
Description = "Create in $SECTION_LABEL",
|
|
328
|
+
CreatedAt = seedDate
|
|
329
|
+
},
|
|
330
|
+
// Update
|
|
331
|
+
new {
|
|
332
|
+
Id = Guid.Parse("$PERM_SECTION_UPDATE_GUID"),
|
|
333
|
+
Path = "$APP.$MODULE.$SECTION.update",
|
|
334
|
+
Level = PermissionLevel.Section,
|
|
335
|
+
Action = PermissionAction.Update,
|
|
336
|
+
IsWildcard = false,
|
|
337
|
+
SectionId = {section}SectionId,
|
|
338
|
+
Description = "Update in $SECTION_LABEL",
|
|
339
|
+
CreatedAt = seedDate
|
|
340
|
+
},
|
|
341
|
+
// Delete
|
|
342
|
+
new {
|
|
343
|
+
Id = Guid.Parse("$PERM_SECTION_DELETE_GUID"),
|
|
344
|
+
Path = "$APP.$MODULE.$SECTION.delete",
|
|
345
|
+
Level = PermissionLevel.Section,
|
|
346
|
+
Action = PermissionAction.Delete,
|
|
347
|
+
IsWildcard = false,
|
|
348
|
+
SectionId = {section}SectionId,
|
|
349
|
+
Description = "Delete in $SECTION_LABEL",
|
|
350
|
+
CreatedAt = seedDate
|
|
351
|
+
},
|
|
352
|
+
// Execute (optional)
|
|
353
|
+
new {
|
|
354
|
+
Id = Guid.Parse("$PERM_SECTION_EXECUTE_GUID"),
|
|
355
|
+
Path = "$APP.$MODULE.$SECTION.execute",
|
|
356
|
+
Level = PermissionLevel.Section,
|
|
357
|
+
Action = PermissionAction.Execute,
|
|
358
|
+
IsWildcard = false,
|
|
359
|
+
SectionId = {section}SectionId,
|
|
360
|
+
Description = "Execute actions in $SECTION_LABEL",
|
|
361
|
+
CreatedAt = seedDate
|
|
362
|
+
}
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
---
|
|
366
|
+
|
|
367
|
+
## TEMPLATE: RESOURCE PERMISSIONS SEED (Level 4)
|
|
368
|
+
|
|
369
|
+
> **Usage:** Finest level - sub-resources with distinct permissions (e.g.: Prompts → Blocks)
|
|
370
|
+
|
|
371
|
+
### Permission Entity - Resource Level
|
|
372
|
+
|
|
373
|
+
```csharp
|
|
374
|
+
// In PermissionConfiguration.cs - HasData()
|
|
375
|
+
// Pattern: {application}.{module}.{section}.{resource}.{action}
|
|
376
|
+
|
|
377
|
+
// Declare ResourceId (must match NavigationResourceConfiguration.cs)
|
|
378
|
+
var {resource}ResourceId = Guid.Parse("$RESOURCE_GUID");
|
|
379
|
+
|
|
380
|
+
// Wildcard Resource
|
|
381
|
+
new {
|
|
382
|
+
Id = Guid.Parse("$PERM_RESOURCE_WILDCARD_GUID"),
|
|
383
|
+
Path = "$APP.$MODULE.$SECTION.$RESOURCE.*",
|
|
384
|
+
Level = PermissionLevel.Resource,
|
|
385
|
+
IsWildcard = true,
|
|
386
|
+
ResourceId = {resource}ResourceId,
|
|
387
|
+
Description = "Full $RESOURCE_LABEL access",
|
|
388
|
+
CreatedAt = seedDate
|
|
389
|
+
},
|
|
390
|
+
// Read
|
|
391
|
+
new {
|
|
392
|
+
Id = Guid.Parse("$PERM_RESOURCE_READ_GUID"),
|
|
393
|
+
Path = "$APP.$MODULE.$SECTION.$RESOURCE.read",
|
|
394
|
+
Level = PermissionLevel.Resource,
|
|
395
|
+
Action = PermissionAction.Read,
|
|
396
|
+
IsWildcard = false,
|
|
397
|
+
ResourceId = {resource}ResourceId,
|
|
398
|
+
Description = "View $RESOURCE_LABEL",
|
|
399
|
+
CreatedAt = seedDate
|
|
400
|
+
},
|
|
401
|
+
// Create
|
|
402
|
+
new {
|
|
403
|
+
Id = Guid.Parse("$PERM_RESOURCE_CREATE_GUID"),
|
|
404
|
+
Path = "$APP.$MODULE.$SECTION.$RESOURCE.create",
|
|
405
|
+
Level = PermissionLevel.Resource,
|
|
406
|
+
Action = PermissionAction.Create,
|
|
407
|
+
IsWildcard = false,
|
|
408
|
+
ResourceId = {resource}ResourceId,
|
|
409
|
+
Description = "Create $RESOURCE_LABEL",
|
|
410
|
+
CreatedAt = seedDate
|
|
411
|
+
},
|
|
412
|
+
// Update
|
|
413
|
+
new {
|
|
414
|
+
Id = Guid.Parse("$PERM_RESOURCE_UPDATE_GUID"),
|
|
415
|
+
Path = "$APP.$MODULE.$SECTION.$RESOURCE.update",
|
|
416
|
+
Level = PermissionLevel.Resource,
|
|
417
|
+
Action = PermissionAction.Update,
|
|
418
|
+
IsWildcard = false,
|
|
419
|
+
ResourceId = {resource}ResourceId,
|
|
420
|
+
Description = "Update $RESOURCE_LABEL",
|
|
421
|
+
CreatedAt = seedDate
|
|
422
|
+
},
|
|
423
|
+
// Delete
|
|
424
|
+
new {
|
|
425
|
+
Id = Guid.Parse("$PERM_RESOURCE_DELETE_GUID"),
|
|
426
|
+
Path = "$APP.$MODULE.$SECTION.$RESOURCE.delete",
|
|
427
|
+
Level = PermissionLevel.Resource,
|
|
428
|
+
Action = PermissionAction.Delete,
|
|
429
|
+
IsWildcard = false,
|
|
430
|
+
ResourceId = {resource}ResourceId,
|
|
431
|
+
Description = "Delete $RESOURCE_LABEL",
|
|
432
|
+
CreatedAt = seedDate
|
|
433
|
+
}
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
---
|
|
437
|
+
|
|
438
|
+
## TEMPLATE: BULK OPERATIONS PERMISSIONS
|
|
439
|
+
|
|
440
|
+
> **MANDATORY:** Always provide for CRUD modules
|
|
441
|
+
|
|
442
|
+
### Permission Entity - Bulk Operations
|
|
443
|
+
|
|
444
|
+
```csharp
|
|
445
|
+
// In PermissionConfiguration.cs - HasData()
|
|
446
|
+
// Add after standard CRUD permissions
|
|
447
|
+
|
|
448
|
+
// Bulk Create
|
|
449
|
+
new {
|
|
450
|
+
Id = Guid.Parse("$PERM_BULK_CREATE_GUID"),
|
|
451
|
+
Path = "$APP.$MODULE.bulk-create",
|
|
452
|
+
Level = PermissionLevel.Module,
|
|
453
|
+
Action = PermissionAction.Create,
|
|
454
|
+
IsWildcard = false,
|
|
455
|
+
ModuleId = {module}ModuleId,
|
|
456
|
+
Description = "Bulk create $MODULE_LABEL",
|
|
457
|
+
CreatedAt = seedDate
|
|
458
|
+
},
|
|
459
|
+
// Bulk Update
|
|
460
|
+
new {
|
|
461
|
+
Id = Guid.Parse("$PERM_BULK_UPDATE_GUID"),
|
|
462
|
+
Path = "$APP.$MODULE.bulk-update",
|
|
463
|
+
Level = PermissionLevel.Module,
|
|
464
|
+
Action = PermissionAction.Update,
|
|
465
|
+
IsWildcard = false,
|
|
466
|
+
ModuleId = {module}ModuleId,
|
|
467
|
+
Description = "Bulk update $MODULE_LABEL",
|
|
468
|
+
CreatedAt = seedDate
|
|
469
|
+
},
|
|
470
|
+
// Bulk Delete
|
|
471
|
+
new {
|
|
472
|
+
Id = Guid.Parse("$PERM_BULK_DELETE_GUID"),
|
|
473
|
+
Path = "$APP.$MODULE.bulk-delete",
|
|
474
|
+
Level = PermissionLevel.Module,
|
|
475
|
+
Action = PermissionAction.Delete,
|
|
476
|
+
IsWildcard = false,
|
|
477
|
+
ModuleId = {module}ModuleId,
|
|
478
|
+
Description = "Bulk delete $MODULE_LABEL",
|
|
479
|
+
CreatedAt = seedDate
|
|
480
|
+
},
|
|
481
|
+
// Export
|
|
482
|
+
new {
|
|
483
|
+
Id = Guid.Parse("$PERM_EXPORT_GUID"),
|
|
484
|
+
Path = "$APP.$MODULE.export",
|
|
485
|
+
Level = PermissionLevel.Module,
|
|
486
|
+
Action = PermissionAction.Execute,
|
|
487
|
+
IsWildcard = false,
|
|
488
|
+
ModuleId = {module}ModuleId,
|
|
489
|
+
Description = "Export $MODULE_LABEL data",
|
|
490
|
+
CreatedAt = seedDate
|
|
491
|
+
},
|
|
492
|
+
// Import
|
|
493
|
+
new {
|
|
494
|
+
Id = Guid.Parse("$PERM_IMPORT_GUID"),
|
|
495
|
+
Path = "$APP.$MODULE.import",
|
|
496
|
+
Level = PermissionLevel.Module,
|
|
497
|
+
Action = PermissionAction.Create,
|
|
498
|
+
IsWildcard = false,
|
|
499
|
+
ModuleId = {module}ModuleId,
|
|
500
|
+
Description = "Import $MODULE_LABEL data",
|
|
501
|
+
CreatedAt = seedDate
|
|
502
|
+
}
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
---
|
|
506
|
+
|
|
507
|
+
## COMPLETE EXAMPLE: "Products" Module in "Sales"
|
|
508
|
+
|
|
509
|
+
### Variables
|
|
510
|
+
|
|
511
|
+
```
|
|
512
|
+
$APP = sales
|
|
513
|
+
$APP_GUID = e2e2e2e2-2222-2222-2222-222222222222
|
|
514
|
+
$MODULE = products
|
|
515
|
+
$MODULE_GUID = e3e3e3e3-3333-3333-3333-333333333333
|
|
516
|
+
|
|
517
|
+
$LABEL_FR = Produits
|
|
518
|
+
$LABEL_EN = Products
|
|
519
|
+
$LABEL_IT = Prodotti
|
|
520
|
+
$LABEL_DE = Produkte
|
|
521
|
+
|
|
522
|
+
$DESC_FR = Gestion des produits
|
|
523
|
+
$DESC_EN = Product management
|
|
524
|
+
$DESC_IT = Gestione prodotti
|
|
525
|
+
$DESC_DE = Produktverwaltung
|
|
526
|
+
|
|
527
|
+
$ICON = Package
|
|
528
|
+
$ORDER = 1
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
### Files to modify
|
|
532
|
+
|
|
533
|
+
1. `NavigationApplicationConfiguration.cs` - Add Sales application
|
|
534
|
+
2. `NavigationModuleConfiguration.cs` - Add Products module
|
|
535
|
+
3. `NavigationTranslationConfiguration.cs` - Add 4 translations × number of entities
|
|
536
|
+
4. `PermissionConfiguration.cs` - Add CRUD permissions
|
|
537
|
+
5. `RolePermissionConfiguration.cs` - Assign to default roles
|
|
538
|
+
|
|
539
|
+
---
|
|
540
|
+
|
|
541
|
+
## TEMPLATE: ENTITY SEED DATA (SeedData Provider)
|
|
542
|
+
|
|
543
|
+
> **Usage:** Runtime seed data for domain entities (Products, Orders, Tickets, etc.)
|
|
544
|
+
> **When:** User wants initial data for development/testing
|
|
545
|
+
> **Mechanism:** Loaded at startup by DevDataSeeder, NOT via EF Core HasData/migrations
|
|
546
|
+
> **Architecture:** Identical in core AND extensions
|
|
547
|
+
|
|
548
|
+
### Two Seeding Mechanisms
|
|
549
|
+
|
|
550
|
+
| Mechanism | When to Use | Applied Via |
|
|
551
|
+
|-----------|-------------|-------------|
|
|
552
|
+
| **HasData()** in Configuration.cs | System entities (Navigation, Permissions, Roles) | EF Core migrations |
|
|
553
|
+
| **SeedData.cs + DevDataSeeder** | Domain entities (Tickets, Products, Orders) | Application startup |
|
|
554
|
+
|
|
555
|
+
**Rule:** Navigation, Permission, and RolePermission seeds use HasData().
|
|
556
|
+
Domain entity seeds use the SeedData provider pattern below.
|
|
557
|
+
|
|
558
|
+
### SeedData Class Pattern
|
|
559
|
+
|
|
560
|
+
```csharp
|
|
561
|
+
// Infrastructure/Persistence/Seeding/Data/{Domain}/{EntityName}SeedData.cs
|
|
562
|
+
|
|
563
|
+
using SmartStack.Domain.{Application}.{Module};
|
|
564
|
+
|
|
565
|
+
namespace SmartStack.Infrastructure.Persistence.Seeding.Data.{Domain};
|
|
566
|
+
|
|
567
|
+
/// <summary>
|
|
568
|
+
/// Demo {EntityName} data for development and testing.
|
|
569
|
+
/// </summary>
|
|
570
|
+
public static class {EntityName}SeedData
|
|
571
|
+
{
|
|
572
|
+
// ============================================================
|
|
573
|
+
// DETERMINISTIC IDs - for referencing in other seed data
|
|
574
|
+
// ============================================================
|
|
575
|
+
|
|
576
|
+
public static readonly Guid Sample1Id = Guid.Parse("$SEED_GUID_1");
|
|
577
|
+
public static readonly Guid Sample2Id = Guid.Parse("$SEED_GUID_2");
|
|
578
|
+
public static readonly Guid Sample3Id = Guid.Parse("$SEED_GUID_3");
|
|
579
|
+
|
|
580
|
+
/// <summary>
|
|
581
|
+
/// Returns all demo {EntityName} entities.
|
|
582
|
+
/// </summary>
|
|
583
|
+
internal static IEnumerable<{EntityName}SeedItem> GetAll{EntityName}s()
|
|
584
|
+
{
|
|
585
|
+
return new[]
|
|
586
|
+
{
|
|
587
|
+
new {EntityName}SeedItem
|
|
588
|
+
{
|
|
589
|
+
Id = Sample1Id,
|
|
590
|
+
// ... entity properties with realistic sample data
|
|
591
|
+
CreatedByUserId = UserSeedData.SystemUserId
|
|
592
|
+
},
|
|
593
|
+
new {EntityName}SeedItem
|
|
594
|
+
{
|
|
595
|
+
Id = Sample2Id,
|
|
596
|
+
// ... varied data
|
|
597
|
+
CreatedByUserId = UserSeedData.SystemUserId
|
|
598
|
+
},
|
|
599
|
+
new {EntityName}SeedItem
|
|
600
|
+
{
|
|
601
|
+
Id = Sample3Id,
|
|
602
|
+
// ... varied data
|
|
603
|
+
CreatedByUserId = UserSeedData.SystemUserId
|
|
604
|
+
}
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
/// <summary>
|
|
610
|
+
/// Seed item DTO for {EntityName} (avoids domain factory methods during seeding).
|
|
611
|
+
/// </summary>
|
|
612
|
+
internal class {EntityName}SeedItem
|
|
613
|
+
{
|
|
614
|
+
public Guid Id { get; init; }
|
|
615
|
+
// ... all required properties matching the domain entity
|
|
616
|
+
public Guid CreatedByUserId { get; init; }
|
|
617
|
+
}
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
### Multi-Tenant Entity Seed Pattern
|
|
621
|
+
|
|
622
|
+
For entities belonging to a tenant, organize by tenant:
|
|
623
|
+
|
|
624
|
+
```csharp
|
|
625
|
+
internal static IEnumerable<{EntityName}SeedItem> GetAll{EntityName}s()
|
|
626
|
+
{
|
|
627
|
+
var items = new List<{EntityName}SeedItem>();
|
|
628
|
+
|
|
629
|
+
items.AddRange(GetTenant1{EntityName}s());
|
|
630
|
+
items.AddRange(GetTenant2{EntityName}s());
|
|
631
|
+
|
|
632
|
+
return items;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
private static IEnumerable<{EntityName}SeedItem> GetTenant1{EntityName}s()
|
|
636
|
+
{
|
|
637
|
+
var tenantId = TenantSeedData.AcmeCorporationId;
|
|
638
|
+
var userId = TenantMembershipSeedData.AcmeOwnerId;
|
|
639
|
+
|
|
640
|
+
return new[]
|
|
641
|
+
{
|
|
642
|
+
new {EntityName}SeedItem
|
|
643
|
+
{
|
|
644
|
+
Id = Guid.Parse("$GUID"),
|
|
645
|
+
TenantId = tenantId,
|
|
646
|
+
CreatedByUserId = userId,
|
|
647
|
+
// ... properties
|
|
648
|
+
}
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
```
|
|
652
|
+
|
|
653
|
+
### DevDataSeeder Registration Pattern
|
|
654
|
+
|
|
655
|
+
```csharp
|
|
656
|
+
// In Infrastructure/Persistence/Seeding/DevDataSeeder.cs
|
|
657
|
+
|
|
658
|
+
// 1. Add using statement
|
|
659
|
+
using SmartStack.Infrastructure.Persistence.Seeding.Data.{Domain};
|
|
660
|
+
|
|
661
|
+
// 2. Add call in SeedAsync() method (after existing seed calls)
|
|
662
|
+
public async Task SeedAsync(CancellationToken cancellationToken = default)
|
|
663
|
+
{
|
|
664
|
+
// ... existing seed calls ...
|
|
665
|
+
|
|
666
|
+
await Seed{EntityName}sAsync(cancellationToken); // <-- ADD THIS
|
|
667
|
+
|
|
668
|
+
// ... summary logging ...
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
// 3. Add private seeding method
|
|
672
|
+
private async Task Seed{EntityName}sAsync(CancellationToken cancellationToken)
|
|
673
|
+
{
|
|
674
|
+
_logger.LogInformation("Seeding demo {EntityName}s...");
|
|
675
|
+
|
|
676
|
+
var existingCount = await _context.{EntityName}s.CountAsync(cancellationToken);
|
|
677
|
+
if (existingCount > 0)
|
|
678
|
+
{
|
|
679
|
+
_logger.LogWarning("{EntityName}s already seeded ({Count} exist), skipping.", existingCount);
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
var createdCount = 0;
|
|
684
|
+
|
|
685
|
+
foreach (var seedItem in {EntityName}SeedData.GetAll{EntityName}s())
|
|
686
|
+
{
|
|
687
|
+
var entity = {EntityName}.Create(
|
|
688
|
+
// Map seedItem properties to factory method parameters
|
|
689
|
+
);
|
|
690
|
+
|
|
691
|
+
// Set deterministic ID (factory generates random GUID)
|
|
692
|
+
typeof({EntityName}).GetProperty("Id")?.SetValue(entity, seedItem.Id);
|
|
693
|
+
|
|
694
|
+
_context.{EntityName}s.Add(entity);
|
|
695
|
+
createdCount++;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
if (createdCount > 0)
|
|
699
|
+
{
|
|
700
|
+
await _context.SaveChangesAsync(cancellationToken);
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
_logger.LogInformation("Created {Count} demo {EntityName}s.", createdCount);
|
|
704
|
+
}
|
|
705
|
+
```
|
|
706
|
+
|
|
707
|
+
### Seed Data GUID Generation
|
|
708
|
+
|
|
709
|
+
```csharp
|
|
710
|
+
// Option 1: Hardcoded descriptive GUIDs (preferred for small sets)
|
|
711
|
+
public static readonly Guid ProductAlphaId = Guid.Parse("aa000001-0000-0000-0000-000000000001");
|
|
712
|
+
public static readonly Guid ProductBetaId = Guid.Parse("aa000001-0000-0000-0000-000000000002");
|
|
713
|
+
|
|
714
|
+
// Option 2: SHA256-based for larger sets
|
|
715
|
+
private static Guid GenerateEntityGuid(string uniqueKey)
|
|
716
|
+
{
|
|
717
|
+
using var sha256 = System.Security.Cryptography.SHA256.Create();
|
|
718
|
+
var hash = sha256.ComputeHash(System.Text.Encoding.UTF8.GetBytes($"{EntityName}-{uniqueKey}"));
|
|
719
|
+
return new Guid(hash.Take(16).ToArray());
|
|
720
|
+
}
|
|
721
|
+
```
|
|
722
|
+
|
|
723
|
+
### Best Practices
|
|
724
|
+
|
|
725
|
+
| Practice | Description |
|
|
726
|
+
|----------|-------------|
|
|
727
|
+
| Deterministic IDs | NEVER use `Guid.NewGuid()` - seeds must be idempotent |
|
|
728
|
+
| Idempotent | Always check `if exists` before creating |
|
|
729
|
+
| Use factory methods | Call `Entity.Create(...)` not `new Entity()` |
|
|
730
|
+
| Set ID via reflection | Factory methods generate random IDs; override after creation |
|
|
731
|
+
| Realistic data | Use plausible names, descriptions, amounts |
|
|
732
|
+
| Cover all states | Include entities in different statuses (active, closed, etc.) |
|
|
733
|
+
| Reference existing seeds | Use IDs from TenantSeedData, UserSeedData for foreign keys |
|
|
734
|
+
| SaveChanges per batch | Call SaveChanges after each entity group |
|
|
735
|
+
|
|
736
|
+
---
|
|
737
|
+
|
|
738
|
+
## TEMPLATE: CLIENT SEED DATA PROVIDER
|
|
739
|
+
|
|
740
|
+
> **Usage:** Client projects (projectType: "client") to seed data into the Core schema
|
|
741
|
+
> **Mechanism:** Runtime seeding via IClientSeedDataProvider (no Core migrations required)
|
|
742
|
+
> **When:** Generated automatically at step 03b if the project is of type client
|
|
743
|
+
|
|
744
|
+
### Difference with HasData()
|
|
745
|
+
|
|
746
|
+
| Aspect | HasData() (core) | IClientSeedDataProvider (client) |
|
|
747
|
+
|--------|-----------------|----------------------------------|
|
|
748
|
+
| When | EF Core migration | Application startup |
|
|
749
|
+
| Where | In Core migrations | Runtime via CoreDbContext |
|
|
750
|
+
| Who | SmartStack.app | Client project |
|
|
751
|
+
| Idempotent | Yes (single migration) | Yes (check existence) |
|
|
752
|
+
| Core migration | Yes | No |
|
|
753
|
+
| Entities created with | Anonymous objects | Factory methods (mandatory) |
|
|
754
|
+
|
|
755
|
+
### Implementation Pattern
|
|
756
|
+
|
|
757
|
+
The provider is generated in:
|
|
758
|
+
`Infrastructure/Persistence/Seeding/{AppPascalName}SeedDataProvider.cs`
|
|
759
|
+
|
|
760
|
+
It consumes the SeedData files generated in steps 01-03:
|
|
761
|
+
- `{Module}NavigationSeedData.cs` (GUIDs + navigation data)
|
|
762
|
+
- `{Module}NavigationTranslationSeedData.cs` (4-language translations)
|
|
763
|
+
- `{Module}PermissionSeedData.cs` (RBAC permissions)
|
|
764
|
+
- `{Module}RolePermissionSeedData.cs` (role -> permission mappings)
|
|
765
|
+
|
|
766
|
+
### DI Registration
|
|
767
|
+
|
|
768
|
+
In `Infrastructure/DependencyInjection.cs`:
|
|
769
|
+
```csharp
|
|
770
|
+
services.AddScoped<IClientSeedDataProvider, {AppPascalName}SeedDataProvider>();
|
|
771
|
+
```
|
|
772
|
+
|
|
773
|
+
### Execution Pipeline
|
|
774
|
+
|
|
775
|
+
```
|
|
776
|
+
InitializeSmartStackAsync()
|
|
777
|
+
1. MigrateAsync() -> Core schema created
|
|
778
|
+
2. IDatabaseSeeder.SeedAsync() -> System users, tenant
|
|
779
|
+
3. IClientSeedDataProvider.Seed*() -> Navigation + Roles + Permissions + RolePermissions CLIENT
|
|
780
|
+
4. IDevDataSeeder.SeedAsync() -> Dev data
|
|
781
|
+
5. InitializeNavigationRoutingAsync -> Routes loaded (including client routes)
|
|
782
|
+
```
|
|
783
|
+
|
|
784
|
+
### Provider Template
|
|
785
|
+
|
|
786
|
+
```csharp
|
|
787
|
+
using Microsoft.EntityFrameworkCore;
|
|
788
|
+
using SmartStack.Application.Common.Interfaces;
|
|
789
|
+
using SmartStack.Domain.Navigation;
|
|
790
|
+
using SmartStack.Domain.Administration.Roles;
|
|
791
|
+
|
|
792
|
+
namespace {BaseNamespace}.Infrastructure.Persistence.Seeding;
|
|
793
|
+
|
|
794
|
+
public class {AppPascalName}SeedDataProvider : IClientSeedDataProvider
|
|
795
|
+
{
|
|
796
|
+
public int Order => 100;
|
|
797
|
+
|
|
798
|
+
public async Task SeedNavigationAsync(ICoreDbContext context, CancellationToken ct)
|
|
799
|
+
{
|
|
800
|
+
// NOTE: Idempotence is at MODULE level (not application level).
|
|
801
|
+
// If the application already exists, we reuse it and seed any missing modules.
|
|
802
|
+
// This allows adding Module 2+ to an existing application.
|
|
803
|
+
var existingApp = await context.NavigationApplications
|
|
804
|
+
.FirstOrDefaultAsync(a => a.Code == "{app_code}", ct);
|
|
805
|
+
|
|
806
|
+
NavigationApplication app;
|
|
807
|
+
if (existingApp != null)
|
|
808
|
+
{
|
|
809
|
+
app = existingApp; // Application already seeded — reuse for module seeding below
|
|
810
|
+
}
|
|
811
|
+
else
|
|
812
|
+
{
|
|
813
|
+
app = NavigationApplication.Create(
|
|
814
|
+
"{app_code}", "{app_label_en}",
|
|
815
|
+
"{app_desc_en}", "{app_icon}", IconType.Lucide,
|
|
816
|
+
"/{app_code}", {display_order});
|
|
817
|
+
context.NavigationApplications.Add(app);
|
|
818
|
+
await ((DbContext)context).SaveChangesAsync(ct);
|
|
819
|
+
|
|
820
|
+
// Application translations (only when creating new application)
|
|
821
|
+
// foreach (var t in NavigationApplicationSeedData.GetTranslationEntries()) { ... }
|
|
822
|
+
await ((DbContext)context).SaveChangesAsync(ct);
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
// Create modules (idempotent per-module — check each before inserting)
|
|
826
|
+
// var mod1Exists = await context.NavigationModules.AnyAsync(m => m.Code == "{module_code}" && m.ApplicationId == app.Id, ct);
|
|
827
|
+
// if (!mod1Exists) { var mod1 = NavigationModule.Create(...); context.NavigationModules.Add(mod1); }
|
|
828
|
+
|
|
829
|
+
// Module translations — IDEMPOTENT (unique index IX_nav_Translations_EntityType_EntityId_LanguageCode)
|
|
830
|
+
// CRITICAL: Always check before inserting to avoid duplicate key errors on re-runs
|
|
831
|
+
// if (!await context.NavigationTranslations.AnyAsync(
|
|
832
|
+
// t => t.EntityId == {Module}NavigationSeedData.{Module}ModuleId && t.EntityType == NavigationEntityType.Module, ct))
|
|
833
|
+
// { foreach (var t in {Module}NavigationSeedData.GetTranslationEntries()) { ... } }
|
|
834
|
+
|
|
835
|
+
// Sections (idempotent per-section — check each before inserting)
|
|
836
|
+
// var secExists = await context.NavigationSections.AnyAsync(s => s.Code == secEntry.Code && s.ModuleId == ..., ct);
|
|
837
|
+
// Use NavigationSection.Create(moduleId, code, label, description, icon, iconType, route, displayOrder)
|
|
838
|
+
|
|
839
|
+
// Section translations — IDEMPOTENT (same guard pattern as module translations)
|
|
840
|
+
// if (!await context.NavigationTranslations.AnyAsync(
|
|
841
|
+
// t => t.EntityId == secEntry.Id && t.EntityType == NavigationEntityType.Section, ct))
|
|
842
|
+
|
|
843
|
+
// Resources (idempotent per-resource — check each before inserting)
|
|
844
|
+
// var resExists = await context.NavigationResources.AnyAsync(r => r.Code == resEntry.Code && r.SectionId == ..., ct);
|
|
845
|
+
// Use NavigationResource.Create(sectionId, code, label, entityType, route, displayOrder)
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
public async Task SeedRolesAsync(ICoreDbContext context, CancellationToken ct)
|
|
849
|
+
{
|
|
850
|
+
// Check idempotence
|
|
851
|
+
var exists = await context.Roles
|
|
852
|
+
.AnyAsync(r => r.ApplicationId == ApplicationRolesSeedData.ApplicationId, ct);
|
|
853
|
+
if (exists) return;
|
|
854
|
+
|
|
855
|
+
// Create application-scoped roles (Admin, Manager, Contributor, Viewer)
|
|
856
|
+
foreach (var entry in ApplicationRolesSeedData.GetRoleEntries())
|
|
857
|
+
{
|
|
858
|
+
var role = Role.Create(
|
|
859
|
+
entry.Code,
|
|
860
|
+
entry.Name,
|
|
861
|
+
entry.Description,
|
|
862
|
+
entry.ApplicationId,
|
|
863
|
+
entry.IsSystem);
|
|
864
|
+
context.Roles.Add(role);
|
|
865
|
+
}
|
|
866
|
+
await ((DbContext)context).SaveChangesAsync(ct);
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
public async Task SeedPermissionsAsync(ICoreDbContext context, CancellationToken ct)
|
|
870
|
+
{
|
|
871
|
+
var exists = await context.Permissions
|
|
872
|
+
.AnyAsync(p => p.Path == "{full_path}.*", ct);
|
|
873
|
+
if (exists) return;
|
|
874
|
+
|
|
875
|
+
// Create permissions from {Module}PermissionSeedData
|
|
876
|
+
// Use Permission.CreateForModule(...) and Permission.CreateWildcard(...)
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
public async Task SeedRolePermissionsAsync(ICoreDbContext context, CancellationToken ct)
|
|
880
|
+
{
|
|
881
|
+
var exists = await context.RolePermissions
|
|
882
|
+
.AnyAsync(rp => rp.Permission!.Path.StartsWith("{full_path}."), ct);
|
|
883
|
+
if (exists) return;
|
|
884
|
+
|
|
885
|
+
// Create role-permissions from {Module}RolePermissionSeedData
|
|
886
|
+
// Use RolePermission.Create(roleId, permissionId, "system")
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
```
|
|
890
|
+
|
|
891
|
+
### Critical Rules
|
|
892
|
+
|
|
893
|
+
| Rule | Description |
|
|
894
|
+
|------|-------------|
|
|
895
|
+
| Factory methods | `NavigationModule.Create(...)`, `NavigationSection.Create(...)`, `NavigationResource.Create(...)`, `Permission.CreateForModule(...)` - NEVER `new Entity()` |
|
|
896
|
+
| Idempotence | Each Seed method checks existence before inserting |
|
|
897
|
+
| SaveChanges per group | Navigation -> save -> Roles -> save -> Permissions -> save -> RolePermissions -> save |
|
|
898
|
+
| Deterministic GUIDs | Use IDs from SeedData classes (not `Guid.NewGuid()`) |
|
|
899
|
+
| FK resolution by Code | Parent modules found by `Code`, not hardcoded GUID |
|
|
900
|
+
| Section/Resource conditionality | Only generate if `seedDataCore.navigationSections` / `seedDataCore.navigationResources` exist in feature.json |
|
|
901
|
+
|
|
902
|
+
---
|
|
903
|
+
|
|
904
|
+
## SEED CHECKLIST
|
|
905
|
+
|
|
906
|
+
| Check | Status |
|
|
907
|
+
|-------|--------|
|
|
908
|
+
| ☐ Deterministic GUID (not NewGuid) | |
|
|
909
|
+
| ☐ 4 languages for each navigation entity (modules, sections, resources) | |
|
|
910
|
+
| ☐ Index translations continue existing sequence | |
|
|
911
|
+
| ☐ Route aligned with permission path | |
|
|
912
|
+
| ☐ DisplayOrder consistent | |
|
|
913
|
+
| ☐ CRUD permissions created (Level 2 - Module) | |
|
|
914
|
+
| ☐ Section permissions if sub-pages (Level 3) | |
|
|
915
|
+
| ☐ NavigationSections seeded (if `seedDataCore.navigationSections` in feature.json) | |
|
|
916
|
+
| ☐ NavigationResources seeded (if `seedDataCore.navigationResources` in feature.json) | |
|
|
917
|
+
| ☐ Resource permissions if sub-resources (Level 4) | |
|
|
918
|
+
| ☐ Bulk Operations permissions created | |
|
|
919
|
+
| ☐ RolePermissions assigned | |
|
|
920
|
+
| ☐ Entity SeedData.cs created (if user opted in) | |
|
|
921
|
+
| ☐ DevDataSeeder.cs updated (if user opted in) | |
|
|
922
|
+
| ☐ IClientSeedDataProvider generated (client projects only) | |
|
|
923
|
+
| ☐ Provider registered in DI (client projects only) | |
|
|
924
|
+
|
|
925
|
+
---
|
|
926
|
+
|
|
927
|
+
## PERMISSIONS HIERARCHY (4 Levels)
|
|
928
|
+
|
|
929
|
+
```
|
|
930
|
+
Level 1: APPLICATION
|
|
931
|
+
└─ Path: {application}.*
|
|
932
|
+
└─ E.g.: administration.* → Full app access
|
|
933
|
+
|
|
934
|
+
Level 2: MODULE
|
|
935
|
+
└─ Path: {application}.{module}.{action}
|
|
936
|
+
└─ E.g.: administration.users.read
|
|
937
|
+
|
|
938
|
+
Level 3: SECTION
|
|
939
|
+
└─ Path: {application}.{module}.{section}.{action}
|
|
940
|
+
└─ E.g.: administration.ai.settings.update
|
|
941
|
+
|
|
942
|
+
Level 4: RESOURCE (finest granularity)
|
|
943
|
+
└─ Path: {application}.{module}.{section}.{resource}.{action}
|
|
944
|
+
└─ E.g.: administration.ai.prompts.blocks.delete
|
|
945
|
+
```
|
|
946
|
+
|
|
947
|
+
---
|
|
948
|
+
|
|
949
|
+
## SQL OBJECTS INFRASTRUCTURE
|
|
950
|
+
|
|
951
|
+
> **Usage:** SQL Server functions (TVFs, scalar), views, and stored procedures managed as embedded resources
|
|
952
|
+
> **Pattern:** Same as SmartStack.app — `CREATE OR ALTER` for idempotency, applied via migrations
|
|
953
|
+
> **Location:** `Infrastructure/Persistence/SqlObjects/`
|
|
954
|
+
|
|
955
|
+
### Directory Structure
|
|
956
|
+
|
|
957
|
+
```
|
|
958
|
+
Infrastructure/Persistence/SqlObjects/
|
|
959
|
+
├── SqlObjectHelper.cs ← Helper to apply SQL from embedded resources
|
|
960
|
+
├── Functions/ ← Table-Valued & Scalar functions
|
|
961
|
+
│ └── fn_{FunctionName}.sql ← CREATE OR ALTER FUNCTION [schema].[fn_Name]
|
|
962
|
+
├── Views/ ← Future: SQL views
|
|
963
|
+
│ └── vw_{ViewName}.sql
|
|
964
|
+
└── StoredProcedures/ ← Future: Stored procedures
|
|
965
|
+
└── sp_{ProcName}.sql
|
|
966
|
+
```
|
|
967
|
+
|
|
968
|
+
### SqlObjectHelper Pattern
|
|
969
|
+
|
|
970
|
+
```csharp
|
|
971
|
+
// Infrastructure/Persistence/SqlObjects/SqlObjectHelper.cs
|
|
972
|
+
|
|
973
|
+
using System.Reflection;
|
|
974
|
+
using Microsoft.EntityFrameworkCore.Migrations;
|
|
975
|
+
|
|
976
|
+
namespace {BaseNamespace}.Infrastructure.Persistence.SqlObjects;
|
|
977
|
+
|
|
978
|
+
/// <summary>
|
|
979
|
+
/// Helper for applying SQL objects from embedded resources.
|
|
980
|
+
/// SQL files use CREATE OR ALTER for idempotency — safe to re-run on any migration or squash.
|
|
981
|
+
/// </summary>
|
|
982
|
+
public static class SqlObjectHelper
|
|
983
|
+
{
|
|
984
|
+
private static readonly Assembly Assembly = typeof(SqlObjectHelper).Assembly;
|
|
985
|
+
private const string ResourcePrefix = "{BaseNamespace}.Infrastructure.Persistence.SqlObjects.";
|
|
986
|
+
|
|
987
|
+
/// <summary>
|
|
988
|
+
/// Applies all SQL objects (functions, views, stored procedures).
|
|
989
|
+
/// Call in Up() method of a migration, especially after a squash.
|
|
990
|
+
/// </summary>
|
|
991
|
+
public static void ApplyAll(MigrationBuilder migrationBuilder)
|
|
992
|
+
{
|
|
993
|
+
ApplyAllFunctions(migrationBuilder);
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
public static void ApplyAllFunctions(MigrationBuilder migrationBuilder)
|
|
997
|
+
{
|
|
998
|
+
ApplyCategory(migrationBuilder, "Functions");
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
public static void ApplyOne(MigrationBuilder migrationBuilder, string resourceName)
|
|
1002
|
+
{
|
|
1003
|
+
var sql = ReadSqlObject(resourceName);
|
|
1004
|
+
migrationBuilder.Sql(sql);
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
public static string ReadSqlObject(string resourceName)
|
|
1008
|
+
{
|
|
1009
|
+
var fullName = ResourcePrefix + resourceName;
|
|
1010
|
+
using var stream = Assembly.GetManifestResourceStream(fullName)
|
|
1011
|
+
?? throw new InvalidOperationException(
|
|
1012
|
+
$"SQL object resource '{fullName}' not found. " +
|
|
1013
|
+
$"Ensure the .sql file is marked as EmbeddedResource in the .csproj. " +
|
|
1014
|
+
$"Available: {string.Join(", ", Assembly.GetManifestResourceNames().Where(n => n.EndsWith(".sql")))}");
|
|
1015
|
+
using var reader = new StreamReader(stream);
|
|
1016
|
+
return reader.ReadToEnd();
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
private static void ApplyCategory(MigrationBuilder migrationBuilder, string category)
|
|
1020
|
+
{
|
|
1021
|
+
var prefix = ResourcePrefix + category + ".";
|
|
1022
|
+
var resources = Assembly.GetManifestResourceNames()
|
|
1023
|
+
.Where(n => n.StartsWith(prefix, StringComparison.Ordinal)
|
|
1024
|
+
&& n.EndsWith(".sql", StringComparison.Ordinal))
|
|
1025
|
+
.OrderBy(n => n, StringComparer.Ordinal);
|
|
1026
|
+
|
|
1027
|
+
foreach (var resource in resources)
|
|
1028
|
+
{
|
|
1029
|
+
using var stream = Assembly.GetManifestResourceStream(resource)!;
|
|
1030
|
+
using var reader = new StreamReader(stream);
|
|
1031
|
+
migrationBuilder.Sql(reader.ReadToEnd());
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
```
|
|
1036
|
+
|
|
1037
|
+
### .csproj Configuration (REQUIRED)
|
|
1038
|
+
|
|
1039
|
+
```xml
|
|
1040
|
+
<ItemGroup>
|
|
1041
|
+
<EmbeddedResource Include="Persistence\SqlObjects\**\*.sql" />
|
|
1042
|
+
</ItemGroup>
|
|
1043
|
+
```
|
|
1044
|
+
|
|
1045
|
+
### SQL File Convention
|
|
1046
|
+
|
|
1047
|
+
All `.sql` files MUST use `CREATE OR ALTER` for idempotency:
|
|
1048
|
+
|
|
1049
|
+
```sql
|
|
1050
|
+
-- Infrastructure/Persistence/SqlObjects/Functions/fn_GetUserGroupHierarchy.sql
|
|
1051
|
+
CREATE OR ALTER FUNCTION [extensions].[fn_GetEntityHierarchy](@EntityId UNIQUEIDENTIFIER)
|
|
1052
|
+
RETURNS TABLE
|
|
1053
|
+
AS
|
|
1054
|
+
RETURN
|
|
1055
|
+
(
|
|
1056
|
+
WITH Hierarchy AS (
|
|
1057
|
+
SELECT Id, ParentId, 0 AS Level
|
|
1058
|
+
FROM [extensions].[fb_Entities]
|
|
1059
|
+
WHERE Id = @EntityId
|
|
1060
|
+
UNION ALL
|
|
1061
|
+
SELECT child.Id, child.ParentId, parent.Level + 1
|
|
1062
|
+
FROM [extensions].[fb_Entities] child
|
|
1063
|
+
INNER JOIN Hierarchy parent ON child.ParentId = parent.Id
|
|
1064
|
+
WHERE parent.Level < 10
|
|
1065
|
+
)
|
|
1066
|
+
SELECT DISTINCT Id FROM Hierarchy
|
|
1067
|
+
);
|
|
1068
|
+
```
|
|
1069
|
+
|
|
1070
|
+
### Migration Integration
|
|
1071
|
+
|
|
1072
|
+
In a migration's `Up()` method, call SqlObjectHelper to apply/re-apply SQL objects:
|
|
1073
|
+
|
|
1074
|
+
```csharp
|
|
1075
|
+
protected override void Up(MigrationBuilder migrationBuilder)
|
|
1076
|
+
{
|
|
1077
|
+
// ... table creation, indexes, etc.
|
|
1078
|
+
|
|
1079
|
+
// Apply all SQL objects (idempotent — CREATE OR ALTER)
|
|
1080
|
+
SqlObjectHelper.ApplyAll(migrationBuilder);
|
|
1081
|
+
}
|
|
1082
|
+
```
|
|
1083
|
+
|
|
1084
|
+
Or apply a single function:
|
|
1085
|
+
```csharp
|
|
1086
|
+
SqlObjectHelper.ApplyOne(migrationBuilder, "Functions.fn_GetEntityHierarchy.sql");
|
|
1087
|
+
```
|
|
1088
|
+
|
|
1089
|
+
### When to Use SQL Objects
|
|
1090
|
+
|
|
1091
|
+
| Need | Solution |
|
|
1092
|
+
|------|----------|
|
|
1093
|
+
| Recursive hierarchy traversal | TVF with CTE (fn_GetXxxHierarchy) |
|
|
1094
|
+
| Complex aggregation for reports | SQL View (vw_XxxSummary) |
|
|
1095
|
+
| Bulk operations with business logic | Stored Procedure (sp_XxxBatch) |
|
|
1096
|
+
| Simple CRUD, filtering, sorting | EF Core LINQ (no SQL needed) |
|