@atlashub/smartstack-cli 1.36.0 → 1.37.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/package.json +1 -1
- package/templates/skills/application/steps/step-01-navigation.md +226 -43
- package/templates/skills/application/steps/step-03-roles.md +160 -38
- package/templates/skills/application/steps/step-04-backend.md +109 -2
- package/templates/skills/application/templates-seed.md +200 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: step-01-navigation
|
|
3
|
-
description: Generate navigation entity seeds using MCP scaffold_navigation
|
|
3
|
+
description: Generate navigation entity seeds using MCP scaffold_navigation (with fallback)
|
|
4
4
|
prev_step: steps/step-00-init.md
|
|
5
5
|
next_step: steps/step-02-permissions.md
|
|
6
6
|
---
|
|
@@ -9,16 +9,17 @@ next_step: steps/step-02-permissions.md
|
|
|
9
9
|
|
|
10
10
|
## MANDATORY EXECUTION RULES
|
|
11
11
|
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
- ALWAYS verify the output includes translations for 4 languages
|
|
15
|
-
-
|
|
12
|
+
- PREFER MCP `scaffold_navigation` tool as the primary method
|
|
13
|
+
- If MCP is unavailable (`{mcp_available}` = false) or the call fails, use the FALLBACK PROCEDURE below
|
|
14
|
+
- ALWAYS verify the output includes translations for 4 languages (fr, en, it, de)
|
|
15
|
+
- ALWAYS WRITE generated code to the actual Configuration files (not just display)
|
|
16
|
+
- Store navigation GUID for use in subsequent steps
|
|
16
17
|
|
|
17
18
|
## YOUR TASK
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
1. Navigation entity HasData() code
|
|
21
|
-
2. NavigationTranslation entries (4 languages)
|
|
20
|
+
Generate navigation entity seeds:
|
|
21
|
+
1. Navigation entity HasData() code in the appropriate Configuration.cs
|
|
22
|
+
2. NavigationTranslation entries (4 languages) in NavigationTranslationConfiguration.cs
|
|
22
23
|
|
|
23
24
|
---
|
|
24
25
|
|
|
@@ -36,10 +37,11 @@ From step-00-init:
|
|
|
36
37
|
| `{descriptions}` | Object with fr, en, it, de |
|
|
37
38
|
| `{icon}` | Lucide icon name |
|
|
38
39
|
| `{display_order}` | Numeric display order |
|
|
40
|
+
| `{mcp_available}` | Boolean - MCP connectivity status |
|
|
39
41
|
|
|
40
42
|
---
|
|
41
43
|
|
|
42
|
-
## EXECUTION SEQUENCE
|
|
44
|
+
## EXECUTION SEQUENCE (MCP Primary)
|
|
43
45
|
|
|
44
46
|
### 1. Call MCP scaffold_navigation
|
|
45
47
|
|
|
@@ -71,38 +73,19 @@ The tool returns:
|
|
|
71
73
|
- HasData() code for NavigationTranslationConfiguration.cs
|
|
72
74
|
- SeedData class template (optional)
|
|
73
75
|
|
|
74
|
-
### 3.
|
|
76
|
+
### 3. Write Code to Files
|
|
75
77
|
|
|
76
|
-
|
|
77
|
-
## Navigation Seeds Generated
|
|
78
|
-
|
|
79
|
-
**Entity:** {level} - {code}
|
|
80
|
-
**GUID:** {generated_guid}
|
|
81
|
-
**Path:** {full_path}
|
|
82
|
-
|
|
83
|
-
### Files to Update
|
|
84
|
-
|
|
85
|
-
1. **{NavigationLevelConfiguration}.cs**
|
|
86
|
-
[Show HasData code from MCP response]
|
|
87
|
-
|
|
88
|
-
2. **NavigationTranslationConfiguration.cs**
|
|
89
|
-
[Show translation HasData code from MCP response]
|
|
90
|
-
|
|
91
|
-
### Recommended: Create SeedData Class
|
|
78
|
+
**CRITICAL:** WRITE the generated code to the actual Configuration files.
|
|
92
79
|
|
|
93
|
-
|
|
94
|
-
|
|
80
|
+
1. Update `Navigation{Level}Configuration.cs` with the new HasData entry
|
|
81
|
+
2. Update `NavigationTranslationConfiguration.cs` with the 4 translation entries
|
|
95
82
|
|
|
96
83
|
### 4. Store Generated GUID
|
|
97
84
|
|
|
98
|
-
**CRITICAL:** Store the navigation entity GUID for use in step-02-permissions:
|
|
99
|
-
|
|
100
85
|
```
|
|
101
86
|
{navigation_guid} = [GUID from MCP response]
|
|
102
87
|
```
|
|
103
88
|
|
|
104
|
-
This GUID will be referenced when creating permissions.
|
|
105
|
-
|
|
106
89
|
---
|
|
107
90
|
|
|
108
91
|
## MCP RESPONSE HANDLING
|
|
@@ -110,35 +93,235 @@ This GUID will be referenced when creating permissions.
|
|
|
110
93
|
### Success Case
|
|
111
94
|
|
|
112
95
|
If MCP returns successfully:
|
|
113
|
-
-
|
|
96
|
+
- Write HasData code to Configuration files
|
|
114
97
|
- Store `{navigation_guid}` for next step
|
|
115
98
|
- Proceed to step-02-permissions.md
|
|
116
99
|
|
|
117
100
|
### Error Case
|
|
118
101
|
|
|
119
|
-
If MCP call fails:
|
|
120
|
-
-
|
|
121
|
-
-
|
|
122
|
-
- Do NOT
|
|
102
|
+
If MCP call fails or `{mcp_available}` = false:
|
|
103
|
+
- Log the error for reference
|
|
104
|
+
- Proceed to FALLBACK PROCEDURE below
|
|
105
|
+
- Do NOT stop the workflow
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## FALLBACK PROCEDURE (When MCP Unavailable)
|
|
110
|
+
|
|
111
|
+
> This procedure generates navigation seeds following SmartStack.app patterns.
|
|
112
|
+
> Reference: `templates-seed.md` for code templates.
|
|
113
|
+
|
|
114
|
+
### F1. Read Existing Configuration Files
|
|
115
|
+
|
|
116
|
+
**CRITICAL:** Before generating any code, read existing files to determine state:
|
|
117
|
+
|
|
118
|
+
1. **Find the Navigation Configuration directory:**
|
|
119
|
+
```
|
|
120
|
+
Glob: **/Persistence/Configurations/Navigation/Navigation*Configuration.cs
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
2. **Read NavigationTranslationConfiguration.cs** - Find the last GUID index:
|
|
124
|
+
```
|
|
125
|
+
Search for: the last call to GenerateGuid(index++)
|
|
126
|
+
The index variable starts at 1 and increments per translation entry.
|
|
127
|
+
Your new translations MUST continue from the next index value.
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
3. **Read Navigation{Level}Configuration.cs** - Check existing entities:
|
|
131
|
+
```
|
|
132
|
+
Read the Configuration for the target level (Context, Application, Module, Section).
|
|
133
|
+
Check if it already references a SeedData class: builder.HasData(Navigation{Level}SeedData.GetSeedData())
|
|
134
|
+
If yes: Read the corresponding SeedData class to find existing entries.
|
|
135
|
+
If no: You will need to add HasData() call.
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
4. **Read existing SeedData files** (if they exist):
|
|
139
|
+
```
|
|
140
|
+
Glob: **/Seeding/Data/Navigation/Navigation{Level}SeedData.cs
|
|
141
|
+
Check for existing entity IDs to avoid collisions.
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### F2. Determine Parent GUID
|
|
145
|
+
|
|
146
|
+
For non-context levels, find the parent entity GUID:
|
|
147
|
+
|
|
148
|
+
| Level | Parent | Where to Find Parent GUID |
|
|
149
|
+
|-------|--------|---------------------------|
|
|
150
|
+
| application | context | `NavigationContextSeedData.cs` → e.g. `PlatformContextId` |
|
|
151
|
+
| module | application | `NavigationApplicationSeedData.cs` → e.g. `AdministrationAppId` |
|
|
152
|
+
| section | module | `NavigationModuleSeedData.cs` → e.g. `UsersModuleId` |
|
|
153
|
+
|
|
154
|
+
Read the parent SeedData class and find the GUID matching `{parent_path}`.
|
|
155
|
+
|
|
156
|
+
### F3. Generate Navigation Entity GUID
|
|
157
|
+
|
|
158
|
+
Generate a deterministic GUID for the new navigation entity:
|
|
159
|
+
|
|
160
|
+
```csharp
|
|
161
|
+
// Use SHA256 hash of the full_path for deterministic generation
|
|
162
|
+
using var sha256 = System.Security.Cryptography.SHA256.Create();
|
|
163
|
+
var hash = sha256.ComputeHash(Encoding.UTF8.GetBytes("navigation-{level}-{full_path}"));
|
|
164
|
+
var guid = new Guid(hash.Take(16).ToArray());
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
**Rules:**
|
|
168
|
+
- NEVER use `Guid.NewGuid()`
|
|
169
|
+
- Read existing SeedData GUIDs to verify no collision
|
|
170
|
+
- Store result as `{navigation_guid}`
|
|
171
|
+
|
|
172
|
+
### F4. Write Navigation Entity Seed
|
|
173
|
+
|
|
174
|
+
Based on `{level}`, write the seed entry.
|
|
175
|
+
|
|
176
|
+
**Option A: Project uses SeedData classes (SmartStack.app pattern)**
|
|
177
|
+
|
|
178
|
+
If `Navigation{Level}SeedData.cs` exists, add the new entity:
|
|
179
|
+
|
|
180
|
+
```csharp
|
|
181
|
+
// In Infrastructure/Persistence/Seeding/Data/Navigation/Navigation{Level}SeedData.cs
|
|
182
|
+
|
|
183
|
+
// Add static GUID field
|
|
184
|
+
public static readonly Guid {PascalCode}Id = Guid.Parse("{navigation_guid}");
|
|
185
|
+
|
|
186
|
+
// Add to GetSeedData() return array
|
|
187
|
+
new {
|
|
188
|
+
Id = {PascalCode}Id,
|
|
189
|
+
ParentFk = Navigation{ParentLevel}SeedData.{ParentPascalCode}Id, // FK varies by level
|
|
190
|
+
Code = "{code}",
|
|
191
|
+
Label = "{labels.en}",
|
|
192
|
+
Description = "{descriptions.en}",
|
|
193
|
+
Icon = "{icon}",
|
|
194
|
+
IconType = IconType.Lucide,
|
|
195
|
+
Route = "/{full_path_with_slashes}",
|
|
196
|
+
DisplayOrder = {display_order},
|
|
197
|
+
IsActive = true,
|
|
198
|
+
CreatedAt = SeedConstants.SeedDate
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
**FK property by level:**
|
|
203
|
+
|
|
204
|
+
| Level | FK Property | References |
|
|
205
|
+
|-------|-------------|------------|
|
|
206
|
+
| context | (none) | - |
|
|
207
|
+
| application | `ContextId` | NavigationContextSeedData.{Parent}Id |
|
|
208
|
+
| module | `ApplicationId` | NavigationApplicationSeedData.{Parent}Id |
|
|
209
|
+
| section | `ModuleId` | NavigationModuleSeedData.{Parent}Id |
|
|
210
|
+
|
|
211
|
+
**Option B: Project uses inline HasData**
|
|
212
|
+
|
|
213
|
+
If no SeedData class exists, add directly to `Navigation{Level}Configuration.cs`:
|
|
214
|
+
|
|
215
|
+
```csharp
|
|
216
|
+
// In Configure method, add:
|
|
217
|
+
builder.HasData(new {
|
|
218
|
+
Id = Guid.Parse("{navigation_guid}"),
|
|
219
|
+
// ... same properties as Option A
|
|
220
|
+
});
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### F5. Write Translation Entries
|
|
224
|
+
|
|
225
|
+
Add 4 translation entries to `NavigationTranslationConfiguration.cs`:
|
|
226
|
+
|
|
227
|
+
1. Read the file to find the current highest `index` value
|
|
228
|
+
2. Continue from `index + 1`
|
|
229
|
+
3. Use the SAME `GenerateGuid` method already defined in the file:
|
|
230
|
+
|
|
231
|
+
```csharp
|
|
232
|
+
// In GetSeedData() method, add at the end before return:
|
|
233
|
+
|
|
234
|
+
// {level}: {code} translations
|
|
235
|
+
translations.Add(new {
|
|
236
|
+
Id = GenerateGuid(index++),
|
|
237
|
+
EntityType = NavigationEntityType.{Level},
|
|
238
|
+
EntityId = Navigation{Level}SeedData.{PascalCode}Id, // or Guid.Parse("{navigation_guid}")
|
|
239
|
+
LanguageCode = "fr",
|
|
240
|
+
Label = "{labels.fr}",
|
|
241
|
+
Description = "{descriptions.fr}",
|
|
242
|
+
CreatedAt = seedDate
|
|
243
|
+
});
|
|
244
|
+
translations.Add(new {
|
|
245
|
+
Id = GenerateGuid(index++),
|
|
246
|
+
EntityType = NavigationEntityType.{Level},
|
|
247
|
+
EntityId = Navigation{Level}SeedData.{PascalCode}Id,
|
|
248
|
+
LanguageCode = "en",
|
|
249
|
+
Label = "{labels.en}",
|
|
250
|
+
Description = "{descriptions.en}",
|
|
251
|
+
CreatedAt = seedDate
|
|
252
|
+
});
|
|
253
|
+
translations.Add(new {
|
|
254
|
+
Id = GenerateGuid(index++),
|
|
255
|
+
EntityType = NavigationEntityType.{Level},
|
|
256
|
+
EntityId = Navigation{Level}SeedData.{PascalCode}Id,
|
|
257
|
+
LanguageCode = "it",
|
|
258
|
+
Label = "{labels.it}",
|
|
259
|
+
Description = "{descriptions.it}",
|
|
260
|
+
CreatedAt = seedDate
|
|
261
|
+
});
|
|
262
|
+
translations.Add(new {
|
|
263
|
+
Id = GenerateGuid(index++),
|
|
264
|
+
EntityType = NavigationEntityType.{Level},
|
|
265
|
+
EntityId = Navigation{Level}SeedData.{PascalCode}Id,
|
|
266
|
+
LanguageCode = "de",
|
|
267
|
+
Label = "{labels.de}",
|
|
268
|
+
Description = "{descriptions.de}",
|
|
269
|
+
CreatedAt = seedDate
|
|
270
|
+
});
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### F6. Store Result
|
|
274
|
+
|
|
275
|
+
```
|
|
276
|
+
{navigation_guid} = [generated GUID]
|
|
277
|
+
{seed_method} = "fallback" // Indicates MCP was not used
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### F7. Validation Checklist
|
|
281
|
+
|
|
282
|
+
Before proceeding, verify:
|
|
283
|
+
- [ ] Deterministic GUID generated (not NewGuid())
|
|
284
|
+
- [ ] 4 languages present (fr, en, it, de)
|
|
285
|
+
- [ ] Translation index continues existing sequence (no gaps, no collisions)
|
|
286
|
+
- [ ] Parent GUID correctly references existing entity
|
|
287
|
+
- [ ] Route path matches `/{context}/{app}/{module}` pattern
|
|
288
|
+
- [ ] DisplayOrder is consistent with existing entities
|
|
289
|
+
- [ ] Code is WRITTEN to files, not just displayed
|
|
290
|
+
|
|
291
|
+
### F8. Present Summary
|
|
292
|
+
|
|
293
|
+
```markdown
|
|
294
|
+
## Navigation Seeds Generated (Fallback)
|
|
295
|
+
|
|
296
|
+
**Entity:** {level} - {code}
|
|
297
|
+
**GUID:** {navigation_guid}
|
|
298
|
+
**Path:** {full_path}
|
|
299
|
+
|
|
300
|
+
### Files Updated
|
|
301
|
+
|
|
302
|
+
1. **Navigation{Level}SeedData.cs** (or Configuration.cs) - New entity entry
|
|
303
|
+
2. **NavigationTranslationConfiguration.cs** - 4 translation entries added
|
|
304
|
+
```
|
|
123
305
|
|
|
124
306
|
---
|
|
125
307
|
|
|
126
308
|
## SUCCESS METRICS
|
|
127
309
|
|
|
128
|
-
- MCP
|
|
129
|
-
-
|
|
130
|
-
-
|
|
131
|
-
-
|
|
310
|
+
- Navigation entity GUID obtained (via MCP or fallback)
|
|
311
|
+
- HasData code WRITTEN to Configuration files
|
|
312
|
+
- Translation code WRITTEN (4 languages)
|
|
313
|
+
- `{navigation_guid}` stored for step-02
|
|
132
314
|
- Proceeded to step-02-permissions.md
|
|
133
315
|
|
|
134
316
|
## FAILURE MODES
|
|
135
317
|
|
|
136
|
-
- MCP call failed (display error, stop)
|
|
137
318
|
- Missing parent path for non-context level (return to step-00)
|
|
138
319
|
- Invalid level (return to step-00)
|
|
320
|
+
- Parent entity not found in existing seeds (ask user for parent GUID)
|
|
139
321
|
|
|
140
322
|
---
|
|
141
323
|
|
|
142
324
|
## NEXT STEP
|
|
143
325
|
|
|
144
|
-
After
|
|
326
|
+
After navigation seeds are generated (via MCP or fallback) and written to files,
|
|
327
|
+
proceed to `./step-02-permissions.md`
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: step-03-roles
|
|
3
|
-
description: Generate role-permission mappings using MCP scaffold_role_permissions
|
|
3
|
+
description: Generate role-permission mappings using MCP scaffold_role_permissions (with fallback)
|
|
4
4
|
prev_step: steps/step-02-permissions.md
|
|
5
5
|
next_step: steps/step-04-backend.md
|
|
6
6
|
---
|
|
@@ -9,16 +9,18 @@ next_step: steps/step-04-backend.md
|
|
|
9
9
|
|
|
10
10
|
## MANDATORY EXECUTION RULES
|
|
11
11
|
|
|
12
|
-
-
|
|
12
|
+
- PREFER MCP `scaffold_role_permissions` tool as the primary method
|
|
13
|
+
- If MCP is unavailable or the call fails, use the FALLBACK PROCEDURE below
|
|
13
14
|
- ALWAYS assign permissions to default roles
|
|
14
15
|
- NEVER leave permissions without role assignments
|
|
15
|
-
-
|
|
16
|
+
- ALWAYS WRITE generated code to the actual RolePermissionConfiguration.cs file
|
|
16
17
|
|
|
17
18
|
## YOUR TASK
|
|
18
19
|
|
|
19
|
-
|
|
20
|
+
Generate role-permission mappings:
|
|
20
21
|
1. RolePermissionConfiguration.cs HasData() entries
|
|
21
|
-
2. Default role assignments (PlatformAdmin, TenantAdmin, StandardUser)
|
|
22
|
+
2. Default role assignments (SuperAdmin, PlatformAdmin, TenantAdmin, StandardUser)
|
|
23
|
+
3. Application-scoped role assignments (Admin, Manager, Contributor, Viewer)
|
|
22
24
|
|
|
23
25
|
---
|
|
24
26
|
|
|
@@ -31,10 +33,11 @@ From previous steps:
|
|
|
31
33
|
| `{full_path}` | Complete navigation path (navRoute) |
|
|
32
34
|
| `{level}` | context, application, module, or section |
|
|
33
35
|
| `{permission_guids}` | GUIDs for generated permissions |
|
|
36
|
+
| `{mcp_available}` | Boolean - MCP connectivity status |
|
|
34
37
|
|
|
35
38
|
---
|
|
36
39
|
|
|
37
|
-
## EXECUTION SEQUENCE
|
|
40
|
+
## EXECUTION SEQUENCE (MCP Primary)
|
|
38
41
|
|
|
39
42
|
### 1. Determine Default Role Assignments
|
|
40
43
|
|
|
@@ -66,30 +69,24 @@ The tool returns:
|
|
|
66
69
|
- Permission ID variable references
|
|
67
70
|
- Role ID variable references
|
|
68
71
|
|
|
69
|
-
### 4.
|
|
72
|
+
### 4. Write Code to Files
|
|
73
|
+
|
|
74
|
+
**CRITICAL:** WRITE the generated code to the actual RolePermissionConfiguration.cs file.
|
|
75
|
+
|
|
76
|
+
### 5. Present Summary
|
|
70
77
|
|
|
71
78
|
```markdown
|
|
72
79
|
## Role-Permission Mappings
|
|
73
80
|
|
|
74
|
-
### Assigned Permissions
|
|
75
|
-
|
|
76
81
|
| Role | Permissions |
|
|
77
82
|
|------|-------------|
|
|
78
83
|
| SuperAdmin | `{full_path}.*` (via wildcard) |
|
|
79
84
|
| PlatformAdmin | `{full_path}.read`, `.create`, `.update`, `.delete` |
|
|
80
85
|
| TenantAdmin | `{full_path}.read`, `.create`, `.update` |
|
|
81
86
|
| StandardUser | `{full_path}.read` |
|
|
82
|
-
|
|
83
|
-
### RolePermissionConfiguration.cs HasData
|
|
84
|
-
|
|
85
|
-
Add to `Infrastructure/Persistence/Configurations/RolePermissionConfiguration.cs`:
|
|
86
|
-
|
|
87
|
-
[Show HasData entries from MCP response]
|
|
88
87
|
```
|
|
89
88
|
|
|
90
|
-
###
|
|
91
|
-
|
|
92
|
-
If the default role assignments don't match the user's needs:
|
|
89
|
+
### 6. Confirm with User (Optional)
|
|
93
90
|
|
|
94
91
|
```yaml
|
|
95
92
|
questions:
|
|
@@ -107,9 +104,43 @@ questions:
|
|
|
107
104
|
|
|
108
105
|
---
|
|
109
106
|
|
|
110
|
-
##
|
|
107
|
+
## MCP RESPONSE HANDLING
|
|
111
108
|
|
|
112
|
-
|
|
109
|
+
### Success Case
|
|
110
|
+
|
|
111
|
+
If MCP returns successfully:
|
|
112
|
+
- Write RolePermission HasData code to file
|
|
113
|
+
- Show role-permission summary table
|
|
114
|
+
- Proceed to step-04-backend.md
|
|
115
|
+
|
|
116
|
+
### Error Case
|
|
117
|
+
|
|
118
|
+
If MCP call fails or `{mcp_available}` = false:
|
|
119
|
+
- Log the error for reference
|
|
120
|
+
- Proceed to FALLBACK PROCEDURE below
|
|
121
|
+
- Do NOT stop the workflow
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## FALLBACK PROCEDURE (When MCP Unavailable)
|
|
126
|
+
|
|
127
|
+
> This procedure generates role-permission HasData entries following SmartStack.app patterns.
|
|
128
|
+
|
|
129
|
+
### F1. Read Existing RolePermissionConfiguration.cs
|
|
130
|
+
|
|
131
|
+
```
|
|
132
|
+
Glob: **/Persistence/Configurations/Authorization/RolePermissionConfiguration.cs
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Read the file to determine:
|
|
136
|
+
- Existing role-permission mappings
|
|
137
|
+
- The GetSeedData() method structure
|
|
138
|
+
- Which roles already have which permissions
|
|
139
|
+
- The GUID generation method used (deterministic or hardcoded)
|
|
140
|
+
|
|
141
|
+
### F2. Read Role GUIDs
|
|
142
|
+
|
|
143
|
+
**System-level roles** (well-known GUIDs):
|
|
113
144
|
|
|
114
145
|
| Role | GUID |
|
|
115
146
|
|------|------|
|
|
@@ -118,41 +149,132 @@ SmartStack uses well-known GUIDs for default roles:
|
|
|
118
149
|
| TenantAdmin | `33333333-3333-3333-3333-333333333333` |
|
|
119
150
|
| StandardUser | `44444444-4444-4444-4444-444444444444` |
|
|
120
151
|
|
|
121
|
-
|
|
152
|
+
**IMPORTANT:** Read the actual `RoleSeedData.cs` or `RoleConfiguration.cs` in the target project to confirm the actual role GUIDs. The above are defaults; the project may use different values.
|
|
153
|
+
|
|
154
|
+
**Application-scoped roles** (deterministic GUIDs based on application):
|
|
155
|
+
|
|
156
|
+
```csharp
|
|
157
|
+
// Read the existing GenerateDeterministicGuid method in RolePermissionConfiguration.cs
|
|
158
|
+
// Typically uses MD5 hash:
|
|
159
|
+
private static Guid GenerateDeterministicGuid(Guid applicationId, string roleType)
|
|
160
|
+
{
|
|
161
|
+
using var md5 = System.Security.Cryptography.MD5.Create();
|
|
162
|
+
var input = $"{applicationId}-{roleType}";
|
|
163
|
+
var hash = md5.ComputeHash(System.Text.Encoding.UTF8.GetBytes(input));
|
|
164
|
+
return new Guid(hash);
|
|
165
|
+
}
|
|
166
|
+
// roleType values: "admin", "manager", "contributor", "viewer"
|
|
167
|
+
```
|
|
122
168
|
|
|
123
|
-
|
|
169
|
+
Find the `applicationId` from `NavigationApplicationSeedData.cs` matching `{full_path}`.
|
|
124
170
|
|
|
125
|
-
###
|
|
171
|
+
### F3. Determine Context-Based Default Mappings
|
|
126
172
|
|
|
127
|
-
|
|
128
|
-
- Display RolePermission HasData code
|
|
129
|
-
- Show role-permission summary table
|
|
130
|
-
- Proceed to step-04-backend.md
|
|
173
|
+
Based on `{full_path}` prefix:
|
|
131
174
|
|
|
132
|
-
|
|
175
|
+
| Context Prefix | SuperAdmin | PlatformAdmin | App Admin | App Manager | App Contributor | App Viewer |
|
|
176
|
+
|----------------|------------|---------------|-----------|-------------|-----------------|------------|
|
|
177
|
+
| `platform.*` | wildcard | Full CRUD | Full CRUD | CRU | CR | R |
|
|
178
|
+
| `business.*` | wildcard | Full CRUD | Full CRUD | CRU | CR | R |
|
|
179
|
+
| `personal.*` | wildcard | None | Full CRUD | CRU | CR | R |
|
|
180
|
+
|
|
181
|
+
### F4. Generate RolePermission HasData Entries
|
|
182
|
+
|
|
183
|
+
Using `{permission_guids}` from step-02:
|
|
184
|
+
|
|
185
|
+
```csharp
|
|
186
|
+
// In RolePermissionConfiguration.cs - GetSeedData() method
|
|
187
|
+
var seedDate = SeedConstants.SeedDate;
|
|
188
|
+
|
|
189
|
+
// ============================================================
|
|
190
|
+
// {MODULE_NAME} PERMISSIONS
|
|
191
|
+
// ============================================================
|
|
192
|
+
|
|
193
|
+
// SuperAdmin: already has *.* wildcard - no individual entries needed
|
|
194
|
+
|
|
195
|
+
// PlatformAdmin (for platform.* context)
|
|
196
|
+
rolePermissions.Add(new { RoleId = platformAdminRoleId, PermissionId = {permission_guids.read}, AssignedAt = seedDate });
|
|
197
|
+
rolePermissions.Add(new { RoleId = platformAdminRoleId, PermissionId = {permission_guids.create}, AssignedAt = seedDate });
|
|
198
|
+
rolePermissions.Add(new { RoleId = platformAdminRoleId, PermissionId = {permission_guids.update}, AssignedAt = seedDate });
|
|
199
|
+
rolePermissions.Add(new { RoleId = platformAdminRoleId, PermissionId = {permission_guids.delete}, AssignedAt = seedDate });
|
|
200
|
+
|
|
201
|
+
// Application-scoped: Admin → wildcard
|
|
202
|
+
rolePermissions.Add(new { RoleId = appAdminRoleId, PermissionId = {permission_guids.wildcard}, AssignedAt = seedDate });
|
|
203
|
+
|
|
204
|
+
// Application-scoped: Manager → CRUD
|
|
205
|
+
rolePermissions.Add(new { RoleId = appManagerRoleId, PermissionId = {permission_guids.read}, AssignedAt = seedDate });
|
|
206
|
+
rolePermissions.Add(new { RoleId = appManagerRoleId, PermissionId = {permission_guids.create}, AssignedAt = seedDate });
|
|
207
|
+
rolePermissions.Add(new { RoleId = appManagerRoleId, PermissionId = {permission_guids.update}, AssignedAt = seedDate });
|
|
208
|
+
|
|
209
|
+
// Application-scoped: Contributor → CR
|
|
210
|
+
rolePermissions.Add(new { RoleId = appContributorRoleId, PermissionId = {permission_guids.read}, AssignedAt = seedDate });
|
|
211
|
+
rolePermissions.Add(new { RoleId = appContributorRoleId, PermissionId = {permission_guids.create}, AssignedAt = seedDate });
|
|
212
|
+
|
|
213
|
+
// Application-scoped: Viewer → R
|
|
214
|
+
rolePermissions.Add(new { RoleId = appViewerRoleId, PermissionId = {permission_guids.read}, AssignedAt = seedDate });
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### F5. Write Code to RolePermissionConfiguration.cs
|
|
218
|
+
|
|
219
|
+
**CRITICAL:** Do NOT just display code. WRITE it to the actual file.
|
|
220
|
+
|
|
221
|
+
1. Open `RolePermissionConfiguration.cs`
|
|
222
|
+
2. Find the `GetSeedData()` method
|
|
223
|
+
3. Add the new role-permission entries to the list
|
|
224
|
+
4. Add necessary permission GUID references (import from PermissionConfiguration or use inline)
|
|
225
|
+
5. Add comments grouping the new entries: `// {MODULE_NAME} PERMISSIONS`
|
|
226
|
+
|
|
227
|
+
### F6. Present Summary
|
|
228
|
+
|
|
229
|
+
```markdown
|
|
230
|
+
## Role-Permission Mappings Generated (Fallback)
|
|
231
|
+
|
|
232
|
+
| Role | Permissions |
|
|
233
|
+
|------|-------------|
|
|
234
|
+
| SuperAdmin | Already has wildcard access |
|
|
235
|
+
| PlatformAdmin | {full_path}.read, .create, .update, .delete |
|
|
236
|
+
| App Admin | {full_path}.* (wildcard) |
|
|
237
|
+
| App Manager | {full_path}.read, .create, .update |
|
|
238
|
+
| App Contributor | {full_path}.read, .create |
|
|
239
|
+
| App Viewer | {full_path}.read |
|
|
240
|
+
|
|
241
|
+
Written to: RolePermissionConfiguration.cs
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### F7. Offer User Adjustment
|
|
245
|
+
|
|
246
|
+
```yaml
|
|
247
|
+
questions:
|
|
248
|
+
- header: "Role Access"
|
|
249
|
+
question: "Default role-permission mappings have been applied. Adjust?"
|
|
250
|
+
options:
|
|
251
|
+
- label: "Keep defaults (Recommended)"
|
|
252
|
+
description: "Standard role hierarchy applied"
|
|
253
|
+
- label: "Custom adjustments"
|
|
254
|
+
description: "I want to change specific role permissions"
|
|
255
|
+
multiSelect: false
|
|
256
|
+
```
|
|
133
257
|
|
|
134
|
-
If
|
|
135
|
-
- Display error message
|
|
136
|
-
- Suggest checking permission GUIDs
|
|
137
|
-
- Provide manual template as fallback
|
|
258
|
+
If user selects "Custom adjustments", ask which roles/permissions to change and update the file accordingly.
|
|
138
259
|
|
|
139
260
|
---
|
|
140
261
|
|
|
141
262
|
## SUCCESS METRICS
|
|
142
263
|
|
|
143
|
-
- MCP
|
|
144
|
-
-
|
|
264
|
+
- Role-permission mappings generated (via MCP or fallback)
|
|
265
|
+
- RolePermissionConfiguration.cs WRITTEN with new entries
|
|
145
266
|
- All default roles have appropriate access
|
|
146
267
|
- Proceeded to step-04-backend.md
|
|
147
268
|
|
|
148
269
|
## FAILURE MODES
|
|
149
270
|
|
|
150
|
-
-
|
|
151
|
-
-
|
|
152
|
-
- Invalid navRoute (return to step-00)
|
|
271
|
+
- Permission GUIDs not available from step-02 (return to step-02)
|
|
272
|
+
- Role GUIDs not found in project (ask user, use well-known defaults)
|
|
273
|
+
- Invalid navRoute format (return to step-00)
|
|
153
274
|
|
|
154
275
|
---
|
|
155
276
|
|
|
156
277
|
## NEXT STEP
|
|
157
278
|
|
|
158
|
-
After
|
|
279
|
+
After role-permission mappings are generated (via MCP or fallback) and written to files,
|
|
280
|
+
proceed to `./step-04-backend.md`
|
|
@@ -124,7 +124,110 @@ The tool generates (paths organized by navRoute hierarchy `{context}.{applicatio
|
|
|
124
124
|
3. Run: `dotnet ef migrations add core_vX.X.X_XXX_Add{EntityName}`
|
|
125
125
|
```
|
|
126
126
|
|
|
127
|
-
### 6.
|
|
127
|
+
### 6. Entity Seeding (Optional)
|
|
128
|
+
|
|
129
|
+
> Ask the user if they want to seed initial data for the entity.
|
|
130
|
+
> This creates runtime seed data (applied at startup via DevDataSeeder), following the same
|
|
131
|
+
> architecture as core (SeedData provider pattern).
|
|
132
|
+
|
|
133
|
+
```yaml
|
|
134
|
+
questions:
|
|
135
|
+
- header: "Seed Data"
|
|
136
|
+
question: "Would you like to generate initial seed data for {EntityName}?"
|
|
137
|
+
options:
|
|
138
|
+
- label: "Yes - Generate SeedData provider"
|
|
139
|
+
description: "Creates {EntityName}SeedData.cs + registers in DevDataSeeder (same pattern as core)"
|
|
140
|
+
- label: "No - Skip seeding"
|
|
141
|
+
description: "Entity starts empty (can add seed data later)"
|
|
142
|
+
multiSelect: false
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
#### If User Selects YES:
|
|
146
|
+
|
|
147
|
+
**Reference:** See `templates-seed.md` section "TEMPLATE: ENTITY SEED DATA (SeedData Provider)"
|
|
148
|
+
|
|
149
|
+
**Generate the following files:**
|
|
150
|
+
|
|
151
|
+
1. **`Infrastructure/Persistence/Seeding/Data/{Domain}/{EntityName}SeedData.cs`**
|
|
152
|
+
|
|
153
|
+
Follow the SmartStack.app pattern (same architecture as core):
|
|
154
|
+
- Static class with `internal static IEnumerable<{EntityName}SeedItem> GetAll{EntityName}s()`
|
|
155
|
+
- Use deterministic GUIDs (NEVER `Guid.NewGuid()`)
|
|
156
|
+
- Include 3-5 sample entities with varied, realistic data
|
|
157
|
+
- Internal record class `{EntityName}SeedItem` with all entity properties
|
|
158
|
+
- For multi-tenant entities: organize by tenant using `GetTenant1{EntityName}s()` helper methods
|
|
159
|
+
- Reference existing tenant/user IDs from `TenantSeedData`/`UserSeedData` for foreign keys
|
|
160
|
+
|
|
161
|
+
```csharp
|
|
162
|
+
// Infrastructure/Persistence/Seeding/Data/{Domain}/{EntityName}SeedData.cs
|
|
163
|
+
|
|
164
|
+
public static class {EntityName}SeedData
|
|
165
|
+
{
|
|
166
|
+
public static readonly Guid Sample1Id = Guid.Parse("...");
|
|
167
|
+
public static readonly Guid Sample2Id = Guid.Parse("...");
|
|
168
|
+
public static readonly Guid Sample3Id = Guid.Parse("...");
|
|
169
|
+
|
|
170
|
+
internal static IEnumerable<{EntityName}SeedItem> GetAll{EntityName}s()
|
|
171
|
+
{
|
|
172
|
+
return new[]
|
|
173
|
+
{
|
|
174
|
+
new {EntityName}SeedItem { Id = Sample1Id, /* ... */ },
|
|
175
|
+
new {EntityName}SeedItem { Id = Sample2Id, /* ... */ },
|
|
176
|
+
new {EntityName}SeedItem { Id = Sample3Id, /* ... */ }
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
internal class {EntityName}SeedItem
|
|
182
|
+
{
|
|
183
|
+
public Guid Id { get; init; }
|
|
184
|
+
// ... all required entity properties
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
2. **Update `Infrastructure/Persistence/Seeding/DevDataSeeder.cs`**
|
|
189
|
+
- Add `using` for the new SeedData namespace
|
|
190
|
+
- Add `await Seed{EntityName}sAsync(cancellationToken);` in `SeedAsync()` method
|
|
191
|
+
- Add private seeding method:
|
|
192
|
+
|
|
193
|
+
```csharp
|
|
194
|
+
private async Task Seed{EntityName}sAsync(CancellationToken cancellationToken)
|
|
195
|
+
{
|
|
196
|
+
_logger.LogInformation("Seeding demo {EntityName}s...");
|
|
197
|
+
|
|
198
|
+
var existingCount = await _context.{EntityName}s.CountAsync(cancellationToken);
|
|
199
|
+
if (existingCount > 0)
|
|
200
|
+
{
|
|
201
|
+
_logger.LogWarning("{EntityName}s already seeded ({Count} exist), skipping.", existingCount);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
var createdCount = 0;
|
|
206
|
+
foreach (var seedItem in {EntityName}SeedData.GetAll{EntityName}s())
|
|
207
|
+
{
|
|
208
|
+
var entity = {EntityName}.Create(/* map seedItem properties */);
|
|
209
|
+
typeof({EntityName}).GetProperty("Id")?.SetValue(entity, seedItem.Id);
|
|
210
|
+
_context.{EntityName}s.Add(entity);
|
|
211
|
+
createdCount++;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (createdCount > 0)
|
|
215
|
+
await _context.SaveChangesAsync(cancellationToken);
|
|
216
|
+
|
|
217
|
+
_logger.LogInformation("Created {Count} demo {EntityName}s.", createdCount);
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
#### If User Selects NO:
|
|
222
|
+
|
|
223
|
+
Skip this section and proceed to storing entity info.
|
|
224
|
+
|
|
225
|
+
```markdown
|
|
226
|
+
> Skipping entity seed data. You can generate it later using the SeedData provider pattern
|
|
227
|
+
> documented in templates-seed.md.
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### 7. Store Entity Info
|
|
128
231
|
|
|
129
232
|
Store entity information for frontend generation:
|
|
130
233
|
|
|
@@ -132,6 +235,7 @@ Store entity information for frontend generation:
|
|
|
132
235
|
{entity_name} = "{EntityName}"
|
|
133
236
|
{entity_code} = "{code}"
|
|
134
237
|
{api_route} = "/api/{code}"
|
|
238
|
+
{has_seed_data} = true/false // Whether SeedData provider was generated
|
|
135
239
|
```
|
|
136
240
|
|
|
137
241
|
---
|
|
@@ -168,6 +272,7 @@ This ensures:
|
|
|
168
272
|
If MCP returns successfully:
|
|
169
273
|
- Display all generated files
|
|
170
274
|
- Show DbSet and DI registration instructions
|
|
275
|
+
- Ask about entity seeding (section 6)
|
|
171
276
|
- Store entity info for frontend
|
|
172
277
|
- Proceed to step-05-frontend.md
|
|
173
278
|
|
|
@@ -186,6 +291,7 @@ If MCP call fails:
|
|
|
186
291
|
- Entity, Service, Controller generated
|
|
187
292
|
- DTOs generated
|
|
188
293
|
- NavRoute attribute included
|
|
294
|
+
- Entity seeding offered to user (SeedData.cs + DevDataSeeder if accepted)
|
|
189
295
|
- Entity info stored for frontend
|
|
190
296
|
- Proceeded to step-05-frontend.md
|
|
191
297
|
|
|
@@ -199,4 +305,5 @@ If MCP call fails:
|
|
|
199
305
|
|
|
200
306
|
## NEXT STEP
|
|
201
307
|
|
|
202
|
-
After
|
|
308
|
+
After backend code is generated and entity seeding is handled,
|
|
309
|
+
proceed to `./step-05-frontend.md`
|
|
@@ -605,12 +605,209 @@ $ORDER = 1
|
|
|
605
605
|
|
|
606
606
|
---
|
|
607
607
|
|
|
608
|
+
## TEMPLATE: ENTITY SEED DATA (SeedData Provider)
|
|
609
|
+
|
|
610
|
+
> **Usage:** Runtime seed data for domain entities (Products, Orders, Tickets, etc.)
|
|
611
|
+
> **When:** User wants initial data for development/testing
|
|
612
|
+
> **Mechanism:** Loaded at startup by DevDataSeeder, NOT via EF Core HasData/migrations
|
|
613
|
+
> **Architecture:** Identical in core AND extensions
|
|
614
|
+
|
|
615
|
+
### Two Seeding Mechanisms
|
|
616
|
+
|
|
617
|
+
| Mechanism | When to Use | Applied Via |
|
|
618
|
+
|-----------|-------------|-------------|
|
|
619
|
+
| **HasData()** in Configuration.cs | System entities (Navigation, Permissions, Roles) | EF Core migrations |
|
|
620
|
+
| **SeedData.cs + DevDataSeeder** | Domain entities (Tickets, Products, Orders) | Application startup |
|
|
621
|
+
|
|
622
|
+
**Rule:** Navigation, Permission, and RolePermission seeds use HasData().
|
|
623
|
+
Domain entity seeds use the SeedData provider pattern below.
|
|
624
|
+
|
|
625
|
+
### SeedData Class Pattern
|
|
626
|
+
|
|
627
|
+
```csharp
|
|
628
|
+
// Infrastructure/Persistence/Seeding/Data/{Domain}/{EntityName}SeedData.cs
|
|
629
|
+
|
|
630
|
+
using SmartStack.Domain.{Context}.{Application}.{Module};
|
|
631
|
+
|
|
632
|
+
namespace SmartStack.Infrastructure.Persistence.Seeding.Data.{Domain};
|
|
633
|
+
|
|
634
|
+
/// <summary>
|
|
635
|
+
/// Demo {EntityName} data for development and testing.
|
|
636
|
+
/// </summary>
|
|
637
|
+
public static class {EntityName}SeedData
|
|
638
|
+
{
|
|
639
|
+
// ============================================================
|
|
640
|
+
// DETERMINISTIC IDs - for referencing in other seed data
|
|
641
|
+
// ============================================================
|
|
642
|
+
|
|
643
|
+
public static readonly Guid Sample1Id = Guid.Parse("$SEED_GUID_1");
|
|
644
|
+
public static readonly Guid Sample2Id = Guid.Parse("$SEED_GUID_2");
|
|
645
|
+
public static readonly Guid Sample3Id = Guid.Parse("$SEED_GUID_3");
|
|
646
|
+
|
|
647
|
+
/// <summary>
|
|
648
|
+
/// Returns all demo {EntityName} entities.
|
|
649
|
+
/// </summary>
|
|
650
|
+
internal static IEnumerable<{EntityName}SeedItem> GetAll{EntityName}s()
|
|
651
|
+
{
|
|
652
|
+
return new[]
|
|
653
|
+
{
|
|
654
|
+
new {EntityName}SeedItem
|
|
655
|
+
{
|
|
656
|
+
Id = Sample1Id,
|
|
657
|
+
// ... entity properties with realistic sample data
|
|
658
|
+
CreatedByUserId = UserSeedData.SystemUserId
|
|
659
|
+
},
|
|
660
|
+
new {EntityName}SeedItem
|
|
661
|
+
{
|
|
662
|
+
Id = Sample2Id,
|
|
663
|
+
// ... varied data
|
|
664
|
+
CreatedByUserId = UserSeedData.SystemUserId
|
|
665
|
+
},
|
|
666
|
+
new {EntityName}SeedItem
|
|
667
|
+
{
|
|
668
|
+
Id = Sample3Id,
|
|
669
|
+
// ... varied data
|
|
670
|
+
CreatedByUserId = UserSeedData.SystemUserId
|
|
671
|
+
}
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
/// <summary>
|
|
677
|
+
/// Seed item DTO for {EntityName} (avoids domain factory methods during seeding).
|
|
678
|
+
/// </summary>
|
|
679
|
+
internal class {EntityName}SeedItem
|
|
680
|
+
{
|
|
681
|
+
public Guid Id { get; init; }
|
|
682
|
+
// ... all required properties matching the domain entity
|
|
683
|
+
public Guid CreatedByUserId { get; init; }
|
|
684
|
+
}
|
|
685
|
+
```
|
|
686
|
+
|
|
687
|
+
### Multi-Tenant Entity Seed Pattern
|
|
688
|
+
|
|
689
|
+
For entities belonging to a tenant, organize by tenant:
|
|
690
|
+
|
|
691
|
+
```csharp
|
|
692
|
+
internal static IEnumerable<{EntityName}SeedItem> GetAll{EntityName}s()
|
|
693
|
+
{
|
|
694
|
+
var items = new List<{EntityName}SeedItem>();
|
|
695
|
+
|
|
696
|
+
items.AddRange(GetTenant1{EntityName}s());
|
|
697
|
+
items.AddRange(GetTenant2{EntityName}s());
|
|
698
|
+
|
|
699
|
+
return items;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
private static IEnumerable<{EntityName}SeedItem> GetTenant1{EntityName}s()
|
|
703
|
+
{
|
|
704
|
+
var tenantId = TenantSeedData.AcmeCorporationId;
|
|
705
|
+
var userId = TenantMembershipSeedData.AcmeOwnerId;
|
|
706
|
+
|
|
707
|
+
return new[]
|
|
708
|
+
{
|
|
709
|
+
new {EntityName}SeedItem
|
|
710
|
+
{
|
|
711
|
+
Id = Guid.Parse("$GUID"),
|
|
712
|
+
TenantId = tenantId,
|
|
713
|
+
CreatedByUserId = userId,
|
|
714
|
+
// ... properties
|
|
715
|
+
}
|
|
716
|
+
};
|
|
717
|
+
}
|
|
718
|
+
```
|
|
719
|
+
|
|
720
|
+
### DevDataSeeder Registration Pattern
|
|
721
|
+
|
|
722
|
+
```csharp
|
|
723
|
+
// In Infrastructure/Persistence/Seeding/DevDataSeeder.cs
|
|
724
|
+
|
|
725
|
+
// 1. Add using statement
|
|
726
|
+
using SmartStack.Infrastructure.Persistence.Seeding.Data.{Domain};
|
|
727
|
+
|
|
728
|
+
// 2. Add call in SeedAsync() method (after existing seed calls)
|
|
729
|
+
public async Task SeedAsync(CancellationToken cancellationToken = default)
|
|
730
|
+
{
|
|
731
|
+
// ... existing seed calls ...
|
|
732
|
+
|
|
733
|
+
await Seed{EntityName}sAsync(cancellationToken); // <-- ADD THIS
|
|
734
|
+
|
|
735
|
+
// ... summary logging ...
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
// 3. Add private seeding method
|
|
739
|
+
private async Task Seed{EntityName}sAsync(CancellationToken cancellationToken)
|
|
740
|
+
{
|
|
741
|
+
_logger.LogInformation("Seeding demo {EntityName}s...");
|
|
742
|
+
|
|
743
|
+
var existingCount = await _context.{EntityName}s.CountAsync(cancellationToken);
|
|
744
|
+
if (existingCount > 0)
|
|
745
|
+
{
|
|
746
|
+
_logger.LogWarning("{EntityName}s already seeded ({Count} exist), skipping.", existingCount);
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
var createdCount = 0;
|
|
751
|
+
|
|
752
|
+
foreach (var seedItem in {EntityName}SeedData.GetAll{EntityName}s())
|
|
753
|
+
{
|
|
754
|
+
var entity = {EntityName}.Create(
|
|
755
|
+
// Map seedItem properties to factory method parameters
|
|
756
|
+
);
|
|
757
|
+
|
|
758
|
+
// Set deterministic ID (factory generates random GUID)
|
|
759
|
+
typeof({EntityName}).GetProperty("Id")?.SetValue(entity, seedItem.Id);
|
|
760
|
+
|
|
761
|
+
_context.{EntityName}s.Add(entity);
|
|
762
|
+
createdCount++;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
if (createdCount > 0)
|
|
766
|
+
{
|
|
767
|
+
await _context.SaveChangesAsync(cancellationToken);
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
_logger.LogInformation("Created {Count} demo {EntityName}s.", createdCount);
|
|
771
|
+
}
|
|
772
|
+
```
|
|
773
|
+
|
|
774
|
+
### Seed Data GUID Generation
|
|
775
|
+
|
|
776
|
+
```csharp
|
|
777
|
+
// Option 1: Hardcoded descriptive GUIDs (preferred for small sets)
|
|
778
|
+
public static readonly Guid ProductAlphaId = Guid.Parse("aa000001-0000-0000-0000-000000000001");
|
|
779
|
+
public static readonly Guid ProductBetaId = Guid.Parse("aa000001-0000-0000-0000-000000000002");
|
|
780
|
+
|
|
781
|
+
// Option 2: SHA256-based for larger sets
|
|
782
|
+
private static Guid GenerateEntityGuid(string uniqueKey)
|
|
783
|
+
{
|
|
784
|
+
using var sha256 = System.Security.Cryptography.SHA256.Create();
|
|
785
|
+
var hash = sha256.ComputeHash(System.Text.Encoding.UTF8.GetBytes($"{EntityName}-{uniqueKey}"));
|
|
786
|
+
return new Guid(hash.Take(16).ToArray());
|
|
787
|
+
}
|
|
788
|
+
```
|
|
789
|
+
|
|
790
|
+
### Best Practices
|
|
791
|
+
|
|
792
|
+
| Practice | Description |
|
|
793
|
+
|----------|-------------|
|
|
794
|
+
| Deterministic IDs | NEVER use `Guid.NewGuid()` - seeds must be idempotent |
|
|
795
|
+
| Idempotent | Always check `if exists` before creating |
|
|
796
|
+
| Use factory methods | Call `Entity.Create(...)` not `new Entity()` |
|
|
797
|
+
| Set ID via reflection | Factory methods generate random IDs; override after creation |
|
|
798
|
+
| Realistic data | Use plausible names, descriptions, amounts |
|
|
799
|
+
| Cover all states | Include entities in different statuses (active, closed, etc.) |
|
|
800
|
+
| Reference existing seeds | Use IDs from TenantSeedData, UserSeedData for foreign keys |
|
|
801
|
+
| SaveChanges per batch | Call SaveChanges after each entity group |
|
|
802
|
+
|
|
803
|
+
---
|
|
804
|
+
|
|
608
805
|
## SEED CHECKLIST
|
|
609
806
|
|
|
610
807
|
| Check | Status |
|
|
611
808
|
|-------|--------|
|
|
612
809
|
| ☐ Deterministic GUID (not NewGuid) | |
|
|
613
|
-
| ☐ 4 languages for each entity | |
|
|
810
|
+
| ☐ 4 languages for each navigation entity | |
|
|
614
811
|
| ☐ Index translations continue existing sequence | |
|
|
615
812
|
| ☐ Route aligned with permission path | |
|
|
616
813
|
| ☐ DisplayOrder consistent | |
|
|
@@ -619,6 +816,8 @@ $ORDER = 1
|
|
|
619
816
|
| ☐ Resource permissions if sub-resources (Level 5) | |
|
|
620
817
|
| ☐ Bulk Operations permissions created | |
|
|
621
818
|
| ☐ RolePermissions assigned | |
|
|
819
|
+
| ☐ Entity SeedData.cs created (if user opted in) | |
|
|
820
|
+
| ☐ DevDataSeeder.cs updated (if user opted in) | |
|
|
622
821
|
|
|
623
822
|
---
|
|
624
823
|
|