@atlashub/smartstack-mcp 1.2.2 → 1.2.3
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/dist/index.js +404 -17
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/templates/entity-extension.cs.hbs +164 -20
package/dist/index.js
CHANGED
|
@@ -148,6 +148,10 @@ var defaultConfig = {
|
|
|
148
148
|
"loc_",
|
|
149
149
|
"lic_"
|
|
150
150
|
],
|
|
151
|
+
codePrefixes: {
|
|
152
|
+
core: "core_",
|
|
153
|
+
extension: "ext_"
|
|
154
|
+
},
|
|
151
155
|
migrationFormat: "{context}_v{version}_{sequence}_{Description}",
|
|
152
156
|
namespaces: {
|
|
153
157
|
domain: "SmartStack.Domain",
|
|
@@ -226,6 +230,10 @@ function mergeConfig(base, override) {
|
|
|
226
230
|
...base.conventions.schemas,
|
|
227
231
|
...override.conventions?.schemas
|
|
228
232
|
},
|
|
233
|
+
codePrefixes: {
|
|
234
|
+
...base.conventions.codePrefixes,
|
|
235
|
+
...override.conventions?.codePrefixes
|
|
236
|
+
},
|
|
229
237
|
namespaces: {
|
|
230
238
|
...base.conventions.namespaces,
|
|
231
239
|
...override.conventions?.namespaces
|
|
@@ -280,6 +288,10 @@ var ConventionsConfigSchema = z.object({
|
|
|
280
288
|
"loc_",
|
|
281
289
|
"lic_"
|
|
282
290
|
]),
|
|
291
|
+
codePrefixes: z.object({
|
|
292
|
+
core: z.string().default("core_"),
|
|
293
|
+
extension: z.string().default("ext_")
|
|
294
|
+
}),
|
|
283
295
|
migrationFormat: z.string().default("{context}_v{version}_{sequence}_{Description}"),
|
|
284
296
|
namespaces: z.object({
|
|
285
297
|
domain: z.string(),
|
|
@@ -323,7 +335,7 @@ var ConfigSchema = z.object({
|
|
|
323
335
|
});
|
|
324
336
|
var ValidateConventionsInputSchema = z.object({
|
|
325
337
|
path: z.string().optional().describe("Project path to validate (default: SmartStack.app path)"),
|
|
326
|
-
checks: z.array(z.enum(["tables", "migrations", "services", "namespaces", "all"])).default(["all"]).describe("Types of checks to perform")
|
|
338
|
+
checks: z.array(z.enum(["tables", "migrations", "services", "namespaces", "entities", "all"])).default(["all"]).describe("Types of checks to perform")
|
|
327
339
|
});
|
|
328
340
|
var CheckMigrationsInputSchema = z.object({
|
|
329
341
|
projectPath: z.string().optional().describe("EF Core project path"),
|
|
@@ -337,7 +349,10 @@ var ScaffoldExtensionInputSchema = z.object({
|
|
|
337
349
|
namespace: z.string().optional().describe("Custom namespace"),
|
|
338
350
|
baseEntity: z.string().optional().describe("Base entity to extend (for entity type)"),
|
|
339
351
|
methods: z.array(z.string()).optional().describe("Methods to generate (for service type)"),
|
|
340
|
-
outputPath: z.string().optional().describe("Custom output path")
|
|
352
|
+
outputPath: z.string().optional().describe("Custom output path"),
|
|
353
|
+
isSystemEntity: z.boolean().optional().describe("If true, creates a system entity without TenantId"),
|
|
354
|
+
tablePrefix: z.string().optional().describe('Domain prefix for table name (e.g., "auth_", "nav_", "cfg_")'),
|
|
355
|
+
schema: z.enum(["core", "extensions"]).optional().describe("Database schema (default: core)")
|
|
341
356
|
}).optional()
|
|
342
357
|
});
|
|
343
358
|
var ApiDocsInputSchema = z.object({
|
|
@@ -1135,6 +1150,19 @@ var scaffoldExtensionTool = {
|
|
|
1135
1150
|
outputPath: {
|
|
1136
1151
|
type: "string",
|
|
1137
1152
|
description: "Custom output path"
|
|
1153
|
+
},
|
|
1154
|
+
isSystemEntity: {
|
|
1155
|
+
type: "boolean",
|
|
1156
|
+
description: "If true, creates a system entity without TenantId (for entity type)"
|
|
1157
|
+
},
|
|
1158
|
+
tablePrefix: {
|
|
1159
|
+
type: "string",
|
|
1160
|
+
description: 'Domain prefix for table name (e.g., "auth_", "nav_", "cfg_")'
|
|
1161
|
+
},
|
|
1162
|
+
schema: {
|
|
1163
|
+
type: "string",
|
|
1164
|
+
enum: ["core", "extensions"],
|
|
1165
|
+
description: "Database schema (default: core)"
|
|
1138
1166
|
}
|
|
1139
1167
|
}
|
|
1140
1168
|
}
|
|
@@ -1258,40 +1286,166 @@ services.AddScoped<I{{name}}Service, {{name}}Service>();
|
|
|
1258
1286
|
async function scaffoldEntity(name, options, structure, config, result) {
|
|
1259
1287
|
const namespace = options?.namespace || config.conventions.namespaces.domain;
|
|
1260
1288
|
const baseEntity = options?.baseEntity;
|
|
1289
|
+
const isSystemEntity = options?.isSystemEntity || false;
|
|
1290
|
+
const tablePrefix = options?.tablePrefix || "ref_";
|
|
1291
|
+
const schema = options?.schema || config.conventions.schemas.platform;
|
|
1261
1292
|
const entityTemplate = `using System;
|
|
1293
|
+
using SmartStack.Domain.Common;
|
|
1294
|
+
using SmartStack.Domain.Common.Interfaces;
|
|
1262
1295
|
|
|
1263
1296
|
namespace {{namespace}};
|
|
1264
1297
|
|
|
1265
1298
|
/// <summary>
|
|
1266
1299
|
/// {{name}} entity{{#if baseEntity}} extending {{baseEntity}}{{/if}}
|
|
1267
1300
|
/// </summary>
|
|
1268
|
-
|
|
1301
|
+
{{#if isSystemEntity}}
|
|
1302
|
+
public class {{name}} : SystemEntity
|
|
1303
|
+
{{else}}
|
|
1304
|
+
public class {{name}} : BaseEntity
|
|
1305
|
+
{{/if}}
|
|
1269
1306
|
{
|
|
1270
|
-
{{#
|
|
1271
|
-
|
|
1307
|
+
{{#if baseEntity}}
|
|
1308
|
+
/// <summary>
|
|
1309
|
+
/// Foreign key to {{baseEntity}}
|
|
1310
|
+
/// </summary>
|
|
1311
|
+
public Guid {{baseEntity}}Id { get; private set; }
|
|
1312
|
+
|
|
1313
|
+
/// <summary>
|
|
1314
|
+
/// Navigation property to {{baseEntity}}
|
|
1315
|
+
/// </summary>
|
|
1316
|
+
public virtual {{baseEntity}}? {{baseEntity}} { get; private set; }
|
|
1272
1317
|
|
|
1273
|
-
|
|
1318
|
+
{{/if}}
|
|
1319
|
+
// === BUSINESS PROPERTIES ===
|
|
1320
|
+
// TODO: Add {{name}} specific properties here
|
|
1274
1321
|
|
|
1275
|
-
|
|
1322
|
+
/// <summary>
|
|
1323
|
+
/// Private constructor for EF Core
|
|
1324
|
+
/// </summary>
|
|
1325
|
+
private {{name}}() { }
|
|
1276
1326
|
|
|
1277
|
-
|
|
1278
|
-
|
|
1327
|
+
/// <summary>
|
|
1328
|
+
/// Factory method to create a new {{name}}
|
|
1329
|
+
/// </summary>
|
|
1330
|
+
{{#if isSystemEntity}}
|
|
1331
|
+
public static {{name}} Create(
|
|
1332
|
+
string code,
|
|
1333
|
+
string? createdBy = null)
|
|
1334
|
+
{
|
|
1335
|
+
return new {{name}}
|
|
1336
|
+
{
|
|
1337
|
+
Id = Guid.NewGuid(),
|
|
1338
|
+
Code = code.ToLowerInvariant(),
|
|
1339
|
+
CreatedAt = DateTime.UtcNow,
|
|
1340
|
+
CreatedBy = createdBy
|
|
1341
|
+
};
|
|
1342
|
+
}
|
|
1343
|
+
{{else}}
|
|
1344
|
+
public static {{name}} Create(
|
|
1345
|
+
Guid tenantId,
|
|
1346
|
+
string code,
|
|
1347
|
+
string? createdBy = null)
|
|
1348
|
+
{
|
|
1349
|
+
return new {{name}}
|
|
1350
|
+
{
|
|
1351
|
+
Id = Guid.NewGuid(),
|
|
1352
|
+
TenantId = tenantId,
|
|
1353
|
+
Code = code.ToLowerInvariant(),
|
|
1354
|
+
CreatedAt = DateTime.UtcNow,
|
|
1355
|
+
CreatedBy = createdBy
|
|
1356
|
+
};
|
|
1357
|
+
}
|
|
1358
|
+
{{/if}}
|
|
1359
|
+
|
|
1360
|
+
/// <summary>
|
|
1361
|
+
/// Update the entity
|
|
1362
|
+
/// </summary>
|
|
1363
|
+
public void Update(string? updatedBy = null)
|
|
1364
|
+
{
|
|
1365
|
+
UpdatedAt = DateTime.UtcNow;
|
|
1366
|
+
UpdatedBy = updatedBy;
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
/// <summary>
|
|
1370
|
+
/// Soft delete the entity
|
|
1371
|
+
/// </summary>
|
|
1372
|
+
public void SoftDelete(string? deletedBy = null)
|
|
1373
|
+
{
|
|
1374
|
+
IsDeleted = true;
|
|
1375
|
+
DeletedAt = DateTime.UtcNow;
|
|
1376
|
+
DeletedBy = deletedBy;
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
/// <summary>
|
|
1380
|
+
/// Restore a soft-deleted entity
|
|
1381
|
+
/// </summary>
|
|
1382
|
+
public void Restore(string? restoredBy = null)
|
|
1383
|
+
{
|
|
1384
|
+
IsDeleted = false;
|
|
1385
|
+
DeletedAt = null;
|
|
1386
|
+
DeletedBy = null;
|
|
1387
|
+
UpdatedAt = DateTime.UtcNow;
|
|
1388
|
+
UpdatedBy = restoredBy;
|
|
1389
|
+
}
|
|
1279
1390
|
}
|
|
1280
1391
|
`;
|
|
1281
1392
|
const configTemplate = `using Microsoft.EntityFrameworkCore;
|
|
1282
1393
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
|
1394
|
+
using {{domainNamespace}};
|
|
1283
1395
|
|
|
1284
1396
|
namespace {{infrastructureNamespace}}.Persistence.Configurations;
|
|
1285
1397
|
|
|
1286
|
-
public class {{name}}Configuration : IEntityTypeConfiguration<{{
|
|
1398
|
+
public class {{name}}Configuration : IEntityTypeConfiguration<{{name}}>
|
|
1287
1399
|
{
|
|
1288
|
-
public void Configure(EntityTypeBuilder<{{
|
|
1400
|
+
public void Configure(EntityTypeBuilder<{{name}}> builder)
|
|
1289
1401
|
{
|
|
1290
|
-
|
|
1402
|
+
// Table name with schema and domain prefix
|
|
1403
|
+
builder.ToTable("{{tablePrefix}}{{name}}s", "{{schema}}");
|
|
1291
1404
|
|
|
1405
|
+
// Primary key
|
|
1292
1406
|
builder.HasKey(e => e.Id);
|
|
1293
1407
|
|
|
1294
|
-
|
|
1408
|
+
{{#unless isSystemEntity}}
|
|
1409
|
+
// Multi-tenant
|
|
1410
|
+
builder.Property(e => e.TenantId).IsRequired();
|
|
1411
|
+
builder.HasIndex(e => e.TenantId);
|
|
1412
|
+
|
|
1413
|
+
// Code: lowercase, unique per tenant (filtered for soft delete)
|
|
1414
|
+
builder.Property(e => e.Code).HasMaxLength(100).IsRequired();
|
|
1415
|
+
builder.HasIndex(e => new { e.TenantId, e.Code })
|
|
1416
|
+
.IsUnique()
|
|
1417
|
+
.HasFilter("[IsDeleted] = 0")
|
|
1418
|
+
.HasDatabaseName("IX_{{tablePrefix}}{{name}}s_Tenant_Code_Unique");
|
|
1419
|
+
{{else}}
|
|
1420
|
+
// Code: lowercase, unique (filtered for soft delete)
|
|
1421
|
+
builder.Property(e => e.Code).HasMaxLength(100).IsRequired();
|
|
1422
|
+
builder.HasIndex(e => e.Code)
|
|
1423
|
+
.IsUnique()
|
|
1424
|
+
.HasFilter("[IsDeleted] = 0")
|
|
1425
|
+
.HasDatabaseName("IX_{{tablePrefix}}{{name}}s_Code_Unique");
|
|
1426
|
+
{{/unless}}
|
|
1427
|
+
|
|
1428
|
+
// Concurrency token
|
|
1429
|
+
builder.Property(e => e.RowVersion).IsRowVersion();
|
|
1430
|
+
|
|
1431
|
+
// Audit fields
|
|
1432
|
+
builder.Property(e => e.CreatedBy).HasMaxLength(256);
|
|
1433
|
+
builder.Property(e => e.UpdatedBy).HasMaxLength(256);
|
|
1434
|
+
builder.Property(e => e.DeletedBy).HasMaxLength(256);
|
|
1435
|
+
|
|
1436
|
+
{{#if baseEntity}}
|
|
1437
|
+
// Relationship to {{baseEntity}} (1:1)
|
|
1438
|
+
builder.HasOne(e => e.{{baseEntity}})
|
|
1439
|
+
.WithOne()
|
|
1440
|
+
.HasForeignKey<{{name}}>(e => e.{{baseEntity}}Id)
|
|
1441
|
+
.OnDelete(DeleteBehavior.Cascade);
|
|
1442
|
+
|
|
1443
|
+
// Index on foreign key
|
|
1444
|
+
builder.HasIndex(e => e.{{baseEntity}}Id)
|
|
1445
|
+
.IsUnique();
|
|
1446
|
+
{{/if}}
|
|
1447
|
+
|
|
1448
|
+
// TODO: Add additional configuration
|
|
1295
1449
|
}
|
|
1296
1450
|
}
|
|
1297
1451
|
`;
|
|
@@ -1299,9 +1453,11 @@ public class {{name}}Configuration : IEntityTypeConfiguration<{{domainNamespace}
|
|
|
1299
1453
|
namespace,
|
|
1300
1454
|
name,
|
|
1301
1455
|
baseEntity,
|
|
1456
|
+
isSystemEntity,
|
|
1457
|
+
tablePrefix,
|
|
1458
|
+
schema,
|
|
1302
1459
|
infrastructureNamespace: config.conventions.namespaces.infrastructure,
|
|
1303
|
-
domainNamespace: config.conventions.namespaces.domain
|
|
1304
|
-
schema: config.conventions.schemas.platform
|
|
1460
|
+
domainNamespace: config.conventions.namespaces.domain
|
|
1305
1461
|
};
|
|
1306
1462
|
const entityContent = Handlebars.compile(entityTemplate)(context);
|
|
1307
1463
|
const configContent = Handlebars.compile(configTemplate)(context);
|
|
@@ -1319,7 +1475,13 @@ public class {{name}}Configuration : IEntityTypeConfiguration<{{domainNamespace}
|
|
|
1319
1475
|
result.instructions.push(`public DbSet<${name}> ${name}s => Set<${name}>();`);
|
|
1320
1476
|
result.instructions.push("");
|
|
1321
1477
|
result.instructions.push("Create migration:");
|
|
1322
|
-
result.instructions.push(`dotnet ef migrations add
|
|
1478
|
+
result.instructions.push(`dotnet ef migrations add core_vX.X.X_XXX_Add${name} --context ApplicationDbContext`);
|
|
1479
|
+
result.instructions.push("");
|
|
1480
|
+
result.instructions.push("Required fields from BaseEntity:");
|
|
1481
|
+
result.instructions.push(`- Id (Guid), ${isSystemEntity ? "" : "TenantId (Guid), "}Code (string, lowercase)`);
|
|
1482
|
+
result.instructions.push("- CreatedAt, UpdatedAt, CreatedBy, UpdatedBy (audit)");
|
|
1483
|
+
result.instructions.push("- IsDeleted, DeletedAt, DeletedBy (soft delete)");
|
|
1484
|
+
result.instructions.push("- RowVersion (concurrency)");
|
|
1323
1485
|
}
|
|
1324
1486
|
async function scaffoldController(name, options, structure, config, result) {
|
|
1325
1487
|
const namespace = options?.namespace || `${config.conventions.namespaces.api}.Controllers`;
|
|
@@ -1905,7 +2067,7 @@ var conventionsResourceTemplate = {
|
|
|
1905
2067
|
mimeType: "text/markdown"
|
|
1906
2068
|
};
|
|
1907
2069
|
async function getConventionsResource(config) {
|
|
1908
|
-
const { schemas, tablePrefixes, migrationFormat, namespaces, servicePattern } = config.conventions;
|
|
2070
|
+
const { schemas, tablePrefixes, codePrefixes, migrationFormat, namespaces, servicePattern } = config.conventions;
|
|
1909
2071
|
return `# AtlasHub SmartStack Conventions
|
|
1910
2072
|
|
|
1911
2073
|
## Overview
|
|
@@ -1944,6 +2106,37 @@ Tables are organized by domain using prefixes:
|
|
|
1944
2106
|
| \`loc_\` | Localization | loc_Languages, loc_Translations |
|
|
1945
2107
|
| \`lic_\` | Licensing | lic_Licenses |
|
|
1946
2108
|
|
|
2109
|
+
### Navigation Code Prefixes
|
|
2110
|
+
|
|
2111
|
+
All navigation data (Context, Application, Module, Section, Resource) uses code prefixes to distinguish system data from client extensions:
|
|
2112
|
+
|
|
2113
|
+
| Origin | Prefix | Usage | Example |
|
|
2114
|
+
|--------|--------|-------|---------|
|
|
2115
|
+
| SmartStack (system) | \`${codePrefixes.core}\` | Protected, delivered with SmartStack | \`${codePrefixes.core}administration\` |
|
|
2116
|
+
| Client (extension) | \`${codePrefixes.extension}\` | Custom, added by clients | \`${codePrefixes.extension}it\` |
|
|
2117
|
+
|
|
2118
|
+
**Navigation Hierarchy (5 levels):**
|
|
2119
|
+
|
|
2120
|
+
\`\`\`
|
|
2121
|
+
Context \u2192 Application \u2192 Module \u2192 Section \u2192 Resource
|
|
2122
|
+
\`\`\`
|
|
2123
|
+
|
|
2124
|
+
**Examples for each level:**
|
|
2125
|
+
|
|
2126
|
+
| Level | Core Example | Extension Example |
|
|
2127
|
+
|-------|--------------|-------------------|
|
|
2128
|
+
| Context | \`${codePrefixes.core}administration\` | \`${codePrefixes.extension}it\` |
|
|
2129
|
+
| Application | \`${codePrefixes.core}settings\` | \`${codePrefixes.extension}custom_app\` |
|
|
2130
|
+
| Module | \`${codePrefixes.core}users\` | \`${codePrefixes.extension}inventory\` |
|
|
2131
|
+
| Section | \`${codePrefixes.core}management\` | \`${codePrefixes.extension}reports\` |
|
|
2132
|
+
| Resource | \`${codePrefixes.core}user_list\` | \`${codePrefixes.extension}stock_view\` |
|
|
2133
|
+
|
|
2134
|
+
**Rules:**
|
|
2135
|
+
1. \`${codePrefixes.core}*\` codes are **protected** - clients cannot create or modify them
|
|
2136
|
+
2. \`${codePrefixes.extension}*\` codes are **free** - clients can create custom navigation
|
|
2137
|
+
3. SmartStack updates will never overwrite \`${codePrefixes.extension}*\` data
|
|
2138
|
+
4. Codes must be unique within their level (e.g., no two Contexts with same code)
|
|
2139
|
+
|
|
1947
2140
|
### Entity Configuration
|
|
1948
2141
|
|
|
1949
2142
|
\`\`\`csharp
|
|
@@ -2187,6 +2380,197 @@ public interface IUserServiceHooks
|
|
|
2187
2380
|
|
|
2188
2381
|
---
|
|
2189
2382
|
|
|
2383
|
+
## 7. Entity Conventions
|
|
2384
|
+
|
|
2385
|
+
### Required Interfaces
|
|
2386
|
+
|
|
2387
|
+
All entities MUST implement the following interfaces:
|
|
2388
|
+
|
|
2389
|
+
| Interface | Properties | Required |
|
|
2390
|
+
|-----------|------------|----------|
|
|
2391
|
+
| \`ITenantEntity\` | \`TenantId\` (Guid) | **YES** (except system entities) |
|
|
2392
|
+
| \`IHasCode\` | \`Code\` (string, lowercase, max 100) | **YES** |
|
|
2393
|
+
| \`ISoftDeletable\` | \`IsDeleted\`, \`DeletedAt\`, \`DeletedBy\` | **YES** |
|
|
2394
|
+
| \`IVersioned\` | \`RowVersion\` (byte[]) | **YES** |
|
|
2395
|
+
| \`IHistoryEntity<T>\` | \`SourceId\`, \`ChangeType\`, \`OldValues\`, \`NewValues\`, \`ChangedAt\`, \`ChangedBy\` | For history tables |
|
|
2396
|
+
|
|
2397
|
+
### Required Fields (All Entities)
|
|
2398
|
+
|
|
2399
|
+
| Field | Type | Description |
|
|
2400
|
+
|-------|------|-------------|
|
|
2401
|
+
| \`Id\` | \`Guid\` | Primary key |
|
|
2402
|
+
| \`TenantId\` | \`Guid\` | **Tenant identifier** (required for multi-tenant isolation) |
|
|
2403
|
+
| \`Code\` | \`string\` | Unique identifier per tenant (lowercase, max 100) |
|
|
2404
|
+
| \`CreatedAt\` | \`DateTime\` | Creation timestamp |
|
|
2405
|
+
| \`UpdatedAt\` | \`DateTime?\` | Last update timestamp |
|
|
2406
|
+
| \`CreatedBy\` | \`string?\` | User who created the entity |
|
|
2407
|
+
| \`UpdatedBy\` | \`string?\` | User who last updated the entity |
|
|
2408
|
+
| \`IsDeleted\` | \`bool\` | Soft delete flag |
|
|
2409
|
+
| \`DeletedAt\` | \`DateTime?\` | Deletion timestamp |
|
|
2410
|
+
| \`DeletedBy\` | \`string?\` | User who deleted the entity |
|
|
2411
|
+
| \`RowVersion\` | \`byte[]\` | Concurrency token |
|
|
2412
|
+
|
|
2413
|
+
### Multi-Tenant Strategy
|
|
2414
|
+
|
|
2415
|
+
\`\`\`csharp
|
|
2416
|
+
// Interface for tenant-scoped entities
|
|
2417
|
+
public interface ITenantEntity
|
|
2418
|
+
{
|
|
2419
|
+
Guid TenantId { get; set; }
|
|
2420
|
+
}
|
|
2421
|
+
|
|
2422
|
+
// Global query filter (applied automatically)
|
|
2423
|
+
builder.HasQueryFilter(e => e.TenantId == _currentTenantService.TenantId);
|
|
2424
|
+
\`\`\`
|
|
2425
|
+
|
|
2426
|
+
**System Entities (without TenantId):**
|
|
2427
|
+
- Licenses (\`lic_\`)
|
|
2428
|
+
- Global configuration (\`cfg_\` system level)
|
|
2429
|
+
- Shared reference tables
|
|
2430
|
+
|
|
2431
|
+
Use \`ISystemEntity\` marker interface for these entities.
|
|
2432
|
+
|
|
2433
|
+
### Code Field Rules
|
|
2434
|
+
|
|
2435
|
+
1. **Always lowercase** - Normalized via setter and value converter
|
|
2436
|
+
2. **Prefix required**: \`${codePrefixes.core}\` (system) or \`${codePrefixes.extension}\` (extensions)
|
|
2437
|
+
3. **Unique composite index**: \`(TenantId, Code) WHERE IsDeleted = 0\`
|
|
2438
|
+
4. **Max length**: 100 characters
|
|
2439
|
+
|
|
2440
|
+
\`\`\`csharp
|
|
2441
|
+
// Code property with automatic lowercase normalization
|
|
2442
|
+
private string _code = string.Empty;
|
|
2443
|
+
public string Code
|
|
2444
|
+
{
|
|
2445
|
+
get => _code;
|
|
2446
|
+
set => _code = value?.ToLowerInvariant() ?? string.Empty;
|
|
2447
|
+
}
|
|
2448
|
+
\`\`\`
|
|
2449
|
+
|
|
2450
|
+
### Soft Delete with Restore
|
|
2451
|
+
|
|
2452
|
+
\`\`\`csharp
|
|
2453
|
+
public interface ISoftDeletable
|
|
2454
|
+
{
|
|
2455
|
+
bool IsDeleted { get; set; }
|
|
2456
|
+
DateTime? DeletedAt { get; set; }
|
|
2457
|
+
string? DeletedBy { get; set; }
|
|
2458
|
+
}
|
|
2459
|
+
|
|
2460
|
+
// Domain methods
|
|
2461
|
+
public void SoftDelete(string? deletedBy = null)
|
|
2462
|
+
{
|
|
2463
|
+
IsDeleted = true;
|
|
2464
|
+
DeletedAt = DateTime.UtcNow;
|
|
2465
|
+
DeletedBy = deletedBy;
|
|
2466
|
+
}
|
|
2467
|
+
|
|
2468
|
+
public void Restore(string? restoredBy = null)
|
|
2469
|
+
{
|
|
2470
|
+
IsDeleted = false;
|
|
2471
|
+
DeletedAt = null;
|
|
2472
|
+
DeletedBy = null;
|
|
2473
|
+
UpdatedAt = DateTime.UtcNow;
|
|
2474
|
+
UpdatedBy = restoredBy;
|
|
2475
|
+
}
|
|
2476
|
+
\`\`\`
|
|
2477
|
+
|
|
2478
|
+
### History Tables (JSON Pattern)
|
|
2479
|
+
|
|
2480
|
+
\`\`\`csharp
|
|
2481
|
+
public interface IHistoryEntity<TSourceId>
|
|
2482
|
+
{
|
|
2483
|
+
Guid Id { get; set; }
|
|
2484
|
+
TSourceId SourceId { get; set; }
|
|
2485
|
+
ChangeType ChangeType { get; set; } // Created, Updated, Deleted
|
|
2486
|
+
DateTime ChangedAt { get; set; }
|
|
2487
|
+
string? ChangedBy { get; set; }
|
|
2488
|
+
string? OldValues { get; set; } // JSON snapshot before change
|
|
2489
|
+
string? NewValues { get; set; } // JSON snapshot after change
|
|
2490
|
+
}
|
|
2491
|
+
|
|
2492
|
+
public enum ChangeType { Created = 1, Updated = 2, Deleted = 3 }
|
|
2493
|
+
\`\`\`
|
|
2494
|
+
|
|
2495
|
+
### Complete Entity Example
|
|
2496
|
+
|
|
2497
|
+
\`\`\`csharp
|
|
2498
|
+
public class MyEntity : BaseEntity
|
|
2499
|
+
{
|
|
2500
|
+
// === REQUIRED (from BaseEntity) ===
|
|
2501
|
+
// public Guid Id { get; set; }
|
|
2502
|
+
// public Guid TenantId { get; set; }
|
|
2503
|
+
// public string Code { get; set; } // lowercase, unique per tenant
|
|
2504
|
+
// public DateTime CreatedAt { get; set; }
|
|
2505
|
+
// public DateTime? UpdatedAt { get; set; }
|
|
2506
|
+
// public string? CreatedBy { get; set; }
|
|
2507
|
+
// public string? UpdatedBy { get; set; }
|
|
2508
|
+
// public bool IsDeleted { get; set; }
|
|
2509
|
+
// public DateTime? DeletedAt { get; set; }
|
|
2510
|
+
// public string? DeletedBy { get; set; }
|
|
2511
|
+
// public byte[] RowVersion { get; set; }
|
|
2512
|
+
|
|
2513
|
+
// === BUSINESS PROPERTIES ===
|
|
2514
|
+
public string Name { get; private set; } = null!;
|
|
2515
|
+
|
|
2516
|
+
private MyEntity() { }
|
|
2517
|
+
|
|
2518
|
+
public static MyEntity Create(Guid tenantId, string code, string name, string? createdBy = null)
|
|
2519
|
+
{
|
|
2520
|
+
return new MyEntity
|
|
2521
|
+
{
|
|
2522
|
+
Id = Guid.NewGuid(),
|
|
2523
|
+
TenantId = tenantId,
|
|
2524
|
+
Code = code.ToLowerInvariant(),
|
|
2525
|
+
Name = name,
|
|
2526
|
+
CreatedAt = DateTime.UtcNow,
|
|
2527
|
+
CreatedBy = createdBy
|
|
2528
|
+
};
|
|
2529
|
+
}
|
|
2530
|
+
}
|
|
2531
|
+
\`\`\`
|
|
2532
|
+
|
|
2533
|
+
### EF Core Configuration
|
|
2534
|
+
|
|
2535
|
+
\`\`\`csharp
|
|
2536
|
+
public class MyEntityConfiguration : IEntityTypeConfiguration<MyEntity>
|
|
2537
|
+
{
|
|
2538
|
+
public void Configure(EntityTypeBuilder<MyEntity> builder)
|
|
2539
|
+
{
|
|
2540
|
+
builder.ToTable("domain_MyEntities", "${schemas.platform}");
|
|
2541
|
+
builder.HasKey(e => e.Id);
|
|
2542
|
+
|
|
2543
|
+
// Multi-tenant
|
|
2544
|
+
builder.Property(e => e.TenantId).IsRequired();
|
|
2545
|
+
builder.HasIndex(e => e.TenantId);
|
|
2546
|
+
|
|
2547
|
+
// Code: lowercase, unique per tenant (filtered)
|
|
2548
|
+
builder.Property(e => e.Code).HasMaxLength(100).IsRequired();
|
|
2549
|
+
builder.HasIndex(e => new { e.TenantId, e.Code })
|
|
2550
|
+
.IsUnique()
|
|
2551
|
+
.HasFilter("[IsDeleted] = 0");
|
|
2552
|
+
|
|
2553
|
+
// Concurrency
|
|
2554
|
+
builder.Property(e => e.RowVersion).IsRowVersion();
|
|
2555
|
+
|
|
2556
|
+
// Audit fields
|
|
2557
|
+
builder.Property(e => e.CreatedBy).HasMaxLength(256);
|
|
2558
|
+
builder.Property(e => e.UpdatedBy).HasMaxLength(256);
|
|
2559
|
+
builder.Property(e => e.DeletedBy).HasMaxLength(256);
|
|
2560
|
+
}
|
|
2561
|
+
}
|
|
2562
|
+
\`\`\`
|
|
2563
|
+
|
|
2564
|
+
### Entity Types Summary
|
|
2565
|
+
|
|
2566
|
+
| Type | Fields | Use Case |
|
|
2567
|
+
|------|--------|----------|
|
|
2568
|
+
| **BaseEntity** | All required fields | Standard tenant-scoped entities |
|
|
2569
|
+
| **SystemEntity** | All except TenantId | Shared system entities |
|
|
2570
|
+
| **HistoryEntity** | Id, TenantId, SourceId, ChangeType, Values, ChangedAt/By | Audit trail tables |
|
|
2571
|
+
|
|
2572
|
+
---
|
|
2573
|
+
|
|
2190
2574
|
## Quick Reference
|
|
2191
2575
|
|
|
2192
2576
|
| Category | Convention | Example |
|
|
@@ -2194,11 +2578,14 @@ public interface IUserServiceHooks
|
|
|
2194
2578
|
| Platform schema | \`${schemas.platform}\` | \`.ToTable("auth_Users", "${schemas.platform}")\` |
|
|
2195
2579
|
| Extensions schema | \`${schemas.extensions}\` | \`.ToTable("client_Custom", "${schemas.extensions}")\` |
|
|
2196
2580
|
| Table prefixes | \`${tablePrefixes.slice(0, 5).join(", ")}\`, etc. | \`auth_Users\`, \`nav_Modules\` |
|
|
2581
|
+
| Core code prefix | \`${codePrefixes.core}\` | \`${codePrefixes.core}administration\` |
|
|
2582
|
+
| Extension code prefix | \`${codePrefixes.extension}\` | \`${codePrefixes.extension}custom_module\` |
|
|
2197
2583
|
| Migration | \`{context}_v{version}_{seq}_{Desc}\` | \`core_v1.0.0_001_CreateAuthUsers\` |
|
|
2198
2584
|
| Interface | \`I<Name>Service\` | \`IUserService\` |
|
|
2199
2585
|
| Implementation | \`<Name>Service\` | \`UserService\` |
|
|
2200
2586
|
| Domain namespace | \`${namespaces.domain}\` | - |
|
|
2201
2587
|
| API namespace | \`${namespaces.api}\` | - |
|
|
2588
|
+
| Required entity fields | Id, TenantId, Code, Audit, SoftDelete, RowVersion | See Entity Conventions |
|
|
2202
2589
|
`;
|
|
2203
2590
|
}
|
|
2204
2591
|
|