@atlashub/smartstack-cli 4.33.0 → 4.35.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/dist/index.js +45 -0
- package/dist/index.js.map +1 -1
- package/dist/mcp-entry.mjs +6 -293
- package/dist/mcp-entry.mjs.map +1 -1
- package/package.json +1 -1
- package/templates/project/claude-md/api.CLAUDE.md.template +315 -0
- package/templates/project/claude-md/application.CLAUDE.md.template +181 -0
- package/templates/project/claude-md/domain.CLAUDE.md.template +125 -0
- package/templates/project/claude-md/infrastructure.CLAUDE.md.template +168 -0
- package/templates/project/claude-md/root.CLAUDE.md.template +339 -0
- package/templates/project/claude-md/web.CLAUDE.md.template +339 -0
- package/templates/skills/application/templates-frontend.md +62 -93
- package/templates/skills/cli-app-sync/SKILL.md +2 -2
- package/templates/skills/cli-app-sync/references/comparison-map.md +1 -1
- package/templates/skills/documentation/steps/step-03-validate.md +12 -14
- package/templates/mcp-scaffolding/frontend/routes.tsx.hbs +0 -126
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# {{ProjectName}}.Infrastructure - Memory
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
Technical implementations. HOW things work. EF Core, external APIs, file system, email, etc.
|
|
6
|
+
|
|
7
|
+
## Dependencies
|
|
8
|
+
|
|
9
|
+
- **{{ProjectName}}.Domain** (entities, repository interfaces)
|
|
10
|
+
- **{{ProjectName}}.Application** (IExtensionsDbContext, service interfaces)
|
|
11
|
+
- **Microsoft.EntityFrameworkCore.SqlServer**
|
|
12
|
+
- **Microsoft.EntityFrameworkCore.Design**
|
|
13
|
+
|
|
14
|
+
## Structure
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
{{ProjectName}}.Infrastructure/
|
|
18
|
+
├── Persistence/
|
|
19
|
+
│ ├── ExtensionsDbContext.cs → EF Core DbContext (implements IExtensionsDbContext)
|
|
20
|
+
│ ├── Configurations/ → Entity configurations (Fluent API)
|
|
21
|
+
│ │ ├── {Feature}/ → Feature-specific configs
|
|
22
|
+
│ │ └── SchemaConstants.cs → Extensions="extensions"
|
|
23
|
+
│ ├── Migrations/ → EF Core migrations
|
|
24
|
+
│ └── Seeding/ → Database seeders
|
|
25
|
+
├── Services/ → Service implementations
|
|
26
|
+
└── DependencyInjection.cs
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Patterns
|
|
30
|
+
|
|
31
|
+
### DbContext Template
|
|
32
|
+
|
|
33
|
+
```csharp
|
|
34
|
+
namespace {{ProjectName}}.Infrastructure.Persistence;
|
|
35
|
+
|
|
36
|
+
public class ExtensionsDbContext : DbContext, IExtensionsDbContext
|
|
37
|
+
{
|
|
38
|
+
public ExtensionsDbContext(DbContextOptions<ExtensionsDbContext> options)
|
|
39
|
+
: base(options) { }
|
|
40
|
+
|
|
41
|
+
public DbSet<Order> Orders => Set<Order>();
|
|
42
|
+
|
|
43
|
+
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
|
44
|
+
{
|
|
45
|
+
base.OnModelCreating(modelBuilder);
|
|
46
|
+
modelBuilder.HasDefaultSchema(SchemaConstants.Extensions);
|
|
47
|
+
modelBuilder.ApplyConfigurationsFromAssembly(typeof(ExtensionsDbContext).Assembly);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public override async Task<int> SaveChangesAsync(CancellationToken ct = default)
|
|
51
|
+
{
|
|
52
|
+
return await base.SaveChangesAsync(ct);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Entity Configuration Template
|
|
58
|
+
|
|
59
|
+
```csharp
|
|
60
|
+
namespace {{ProjectName}}.Infrastructure.Persistence.Configurations.{Feature};
|
|
61
|
+
|
|
62
|
+
public class OrderConfiguration : IEntityTypeConfiguration<Order>
|
|
63
|
+
{
|
|
64
|
+
public void Configure(EntityTypeBuilder<Order> builder)
|
|
65
|
+
{
|
|
66
|
+
builder.ToTable("ext_Orders", SchemaConstants.Extensions);
|
|
67
|
+
|
|
68
|
+
builder.HasKey(o => o.Id);
|
|
69
|
+
|
|
70
|
+
builder.Property(o => o.Name)
|
|
71
|
+
.IsRequired()
|
|
72
|
+
.HasMaxLength(200);
|
|
73
|
+
|
|
74
|
+
builder.Property(o => o.Amount)
|
|
75
|
+
.HasPrecision(18, 2);
|
|
76
|
+
|
|
77
|
+
builder.HasIndex(o => o.Name);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Repository Implementation Template
|
|
83
|
+
|
|
84
|
+
```csharp
|
|
85
|
+
namespace {{ProjectName}}.Infrastructure.Persistence.Repositories;
|
|
86
|
+
|
|
87
|
+
public class OrderRepository : IOrderRepository
|
|
88
|
+
{
|
|
89
|
+
private readonly ExtensionsDbContext _context;
|
|
90
|
+
|
|
91
|
+
public OrderRepository(ExtensionsDbContext context)
|
|
92
|
+
{
|
|
93
|
+
_context = context;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
public async Task<Order?> GetByIdAsync(Guid id, CancellationToken ct = default)
|
|
97
|
+
=> await _context.Orders.FindAsync(new object[] { id }, ct);
|
|
98
|
+
|
|
99
|
+
public async Task<IReadOnlyList<Order>> GetAllAsync(CancellationToken ct = default)
|
|
100
|
+
=> await _context.Orders.ToListAsync(ct);
|
|
101
|
+
|
|
102
|
+
public async Task AddAsync(Order entity, CancellationToken ct = default)
|
|
103
|
+
=> await _context.Orders.AddAsync(entity, ct);
|
|
104
|
+
|
|
105
|
+
public void Update(Order entity)
|
|
106
|
+
=> _context.Orders.Update(entity);
|
|
107
|
+
|
|
108
|
+
public void Remove(Order entity)
|
|
109
|
+
=> _context.Orders.Remove(entity);
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## DependencyInjection
|
|
114
|
+
|
|
115
|
+
```csharp
|
|
116
|
+
public static class DependencyInjection
|
|
117
|
+
{
|
|
118
|
+
public static IServiceCollection AddInfrastructure(
|
|
119
|
+
this IServiceCollection services,
|
|
120
|
+
IConfiguration configuration)
|
|
121
|
+
{
|
|
122
|
+
services.AddDbContext<ExtensionsDbContext>(options =>
|
|
123
|
+
options.UseSqlServer(
|
|
124
|
+
configuration.GetConnectionString("DefaultConnection"),
|
|
125
|
+
b => b.MigrationsAssembly(typeof(ExtensionsDbContext).Assembly.FullName)));
|
|
126
|
+
|
|
127
|
+
services.AddScoped<IExtensionsDbContext>(provider =>
|
|
128
|
+
provider.GetRequiredService<ExtensionsDbContext>());
|
|
129
|
+
|
|
130
|
+
return services;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## EF Core Commands
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
# Create migration (ExtensionsDbContext)
|
|
139
|
+
dotnet ef migrations add <Name> --context ExtensionsDbContext -p src/{{ProjectName}}.Infrastructure -s src/{{ProjectName}}.Api -o Persistence/Migrations
|
|
140
|
+
|
|
141
|
+
# Update database
|
|
142
|
+
dotnet ef database update --context ExtensionsDbContext -p src/{{ProjectName}}.Infrastructure -s src/{{ProjectName}}.Api
|
|
143
|
+
|
|
144
|
+
# Remove last migration
|
|
145
|
+
dotnet ef migrations remove --context ExtensionsDbContext -p src/{{ProjectName}}.Infrastructure -s src/{{ProjectName}}.Api
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Rules
|
|
149
|
+
|
|
150
|
+
1. **ALL** EF configurations in `Configurations/` folder, organized by feature
|
|
151
|
+
2. **NO** data annotations on entities - use Fluent API only
|
|
152
|
+
3. **Repositories** implement interfaces from Application
|
|
153
|
+
4. **DbContext** implements `IExtensionsDbContext` from Application
|
|
154
|
+
5. **Migrations** stay in this project under `Persistence/Migrations/`
|
|
155
|
+
6. **SQL objects via SqlObjectHelper** - TVFs/views use `SqlObjectHelper.ApplyAll(migrationBuilder)` and embedded `.sql` files
|
|
156
|
+
7. **NO sequential/deterministic GUIDs in seed data** - All GUIDs must be generated via `[guid]::NewGuid()`
|
|
157
|
+
8. **Schema prefix pattern** - Table names use format `{prefix}_{EntityName}` with schema `extensions`. Use `SchemaConstants.Extensions` constant.
|
|
158
|
+
|
|
159
|
+
## When Adding New Entity
|
|
160
|
+
|
|
161
|
+
1. Create entity in `{{ProjectName}}.Domain` with factory method
|
|
162
|
+
2. Create repository interface in `{{ProjectName}}.Application/Common/Interfaces/Persistence/`
|
|
163
|
+
3. Add `DbSet<Entity>` to `ExtensionsDbContext`
|
|
164
|
+
4. Create configuration in `Persistence/Configurations/{Feature}/{Entity}Configuration.cs`
|
|
165
|
+
5. Create repository in `Persistence/Repositories/{Entity}Repository.cs` (if needed)
|
|
166
|
+
6. Register repository in `DependencyInjection.cs`
|
|
167
|
+
7. Create migration: `/efcore:migration Add{Entity}`
|
|
168
|
+
8. Run `/efcore:db-deploy` to apply migration
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
# {{ProjectName}} - Claude Instructions
|
|
2
|
+
|
|
3
|
+
> **{{ProjectName}}** is built on the SmartStack platform (NuGet backend + npm frontend).
|
|
4
|
+
> It extends SmartStack via the **Extensions pattern**: own DbContext, own schema, own modules.
|
|
5
|
+
>
|
|
6
|
+
> 1. SmartStack provides the core platform (auth, navigation, multi-tenant, licensing)
|
|
7
|
+
> 2. {{ProjectName}} adds business-specific modules via `ExtensionsDbContext`
|
|
8
|
+
> 3. Pages are registered via `PageRegistry.register()` — DynamicRouter resolves routes automatically
|
|
9
|
+
> 4. Navigation is managed in DB (admin UI or seed data)
|
|
10
|
+
|
|
11
|
+
> **Priority**: Read sections in order. Earlier sections override later ones.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 1. GOLDEN RULES (ALWAYS APPLY)
|
|
16
|
+
|
|
17
|
+
| # | Rule | Enforcement |
|
|
18
|
+
|---|------|-------------|
|
|
19
|
+
| 1 | **Domain layer has ZERO dependencies** | NEVER import from Application/Infrastructure/Api in Domain |
|
|
20
|
+
| 2 | **Route = Permission path** | `/module/section` → `module.section.*` |
|
|
21
|
+
| 3 | **All GUIDs MUST be random** | Generate via `[guid]::NewGuid()` - NEVER invent patterns |
|
|
22
|
+
| 4 | **SQL objects via SqlObjectHelper** | TVFs/views use `SqlObjectHelper` + embedded `.sql` files. No other raw SQL. |
|
|
23
|
+
| 5 | **Tables use domain prefix** | `ext_Orders`, `ext_Products` in `extensions` schema |
|
|
24
|
+
| 6 | **Auto BugFix Issue after /debug** | After completing a `/debug` skill, ALWAYS run `/bugfix-issue` |
|
|
25
|
+
| 7 | **One migration per feature branch** | Use `/efcore:migration` to manage |
|
|
26
|
+
| 8 | **All code MUST have unit tests** | Backend: xUnit + FluentAssertions. Frontend: Vitest. No PR without tests |
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## 2. GIT RESTRICTIONS
|
|
31
|
+
|
|
32
|
+
### NEVER Execute Directly (propose to user)
|
|
33
|
+
```
|
|
34
|
+
git commit | git push | git merge | git rebase | git cherry-pick | git reset | git revert
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### EXCEPTION: GitFlow Skills
|
|
38
|
+
When user invokes `/gitflow:*` commands → **FOLLOW the skill instructions** and execute git commands as defined by that skill.
|
|
39
|
+
|
|
40
|
+
| Context | Action |
|
|
41
|
+
|---------|--------|
|
|
42
|
+
| Manual git commands | **PROPOSE** only |
|
|
43
|
+
| `/gitflow:*` skills | **FOLLOW skill** and execute |
|
|
44
|
+
|
|
45
|
+
**GitFlow Rule**: Skills define a workflow. Execute commands **as specified by the skill**, not as an automatic pipeline.
|
|
46
|
+
|
|
47
|
+
### Allowed (read-only)
|
|
48
|
+
`git status` | `git log` | `git diff` | `git branch` | `git fetch` | `git worktree list`
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## 3. ARCHITECTURE
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
{{ProjectName}}.sln
|
|
56
|
+
├── src/
|
|
57
|
+
│ ├── {{ProjectName}}.Domain → Business logic (NO dependencies)
|
|
58
|
+
│ ├── {{ProjectName}}.Application → CQRS, use cases (→ Domain)
|
|
59
|
+
│ ├── {{ProjectName}}.Infrastructure → EF Core, repositories, external services (→ Domain, Application)
|
|
60
|
+
│ └── {{ProjectName}}.Api → REST entry point (→ Application, Infrastructure)
|
|
61
|
+
│ └── Controllers/ → Organized by Application/Module
|
|
62
|
+
├── tests/
|
|
63
|
+
│ └── {{ProjectName}}.Tests.Unit → xUnit + FluentAssertions + Moq
|
|
64
|
+
└── web/{{ProjectNameLower}}-web → React + TypeScript + Vite
|
|
65
|
+
├── tests/ → Vitest unit tests (NOT in src/)
|
|
66
|
+
└── src/
|
|
67
|
+
├── pages/ → Organized by Application/Module
|
|
68
|
+
└── components/
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**Dependency flow**: `Domain ← Application ← Infrastructure ← Api`
|
|
72
|
+
|
|
73
|
+
> **Note**: Application references `Microsoft.EntityFrameworkCore` for `DbSet<T>` (used by `IExtensionsDbContext`),
|
|
74
|
+
> `IQueryable<T>` async extensions, and `QueryableExtensions`.
|
|
75
|
+
> This is an intentional architectural tradeoff — the `IExtensionsDbContext` interface remains the abstraction boundary.
|
|
76
|
+
|
|
77
|
+
### Tech Stack
|
|
78
|
+
| Layer | Tech | Version |
|
|
79
|
+
|-------|------|---------|
|
|
80
|
+
| Runtime | .NET | 10.0 |
|
|
81
|
+
| ORM | EF Core | 10.0.1 |
|
|
82
|
+
| Database | SQL Server | Latest |
|
|
83
|
+
| Frontend | React + Vite | 19.x |
|
|
84
|
+
| Language | TypeScript | 5.x |
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## 4. NAVIGATION HIERARCHY
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
Application → Module → Section → Resource
|
|
92
|
+
↓ ↓
|
|
93
|
+
myapp orders
|
|
94
|
+
│ │
|
|
95
|
+
└───────────┴──→ Route: /myapp/orders
|
|
96
|
+
Permission: myapp.orders.{action}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
> **Note**: Applications have an `ApplicationZone` property (enum: `Platform`, `Personal`, `Business`) used for UI layout grouping only. It does NOT appear in routes or permissions.
|
|
100
|
+
|
|
101
|
+
### Permission Actions
|
|
102
|
+
`access` | `read` | `create` | `update` | `delete` | `export` | `import` | `approve` | `reject` | `assign` | `execute`
|
|
103
|
+
|
|
104
|
+
### Adding Navigation
|
|
105
|
+
1. Add HasData in `Navigation*Configuration.cs` (include `ComponentKey` for DynamicRouter)
|
|
106
|
+
2. Run `/efcore:migration AddXxx`
|
|
107
|
+
3. Register page in `componentRegistry.generated.ts` via `PageRegistry.register()`
|
|
108
|
+
4. Add i18n in `navigation.json` (fr/en/it/de — all 4 languages)
|
|
109
|
+
5. Create page component
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## 5. DATABASE CONVENTIONS
|
|
114
|
+
|
|
115
|
+
### Table Naming
|
|
116
|
+
Format: `extensions.{prefix}_{TableName}`
|
|
117
|
+
|
|
118
|
+
> **Note**: {{ProjectName}} uses the `extensions` schema, NOT the `core` schema (which belongs to SmartStack platform).
|
|
119
|
+
|
|
120
|
+
### EF Core Config Pattern
|
|
121
|
+
```csharp
|
|
122
|
+
builder.ToTable("ext_Orders", SchemaConstants.Extensions); // ✅ CORRECT
|
|
123
|
+
builder.ToTable("Orders", "ext"); // ❌ WRONG
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Migration Naming
|
|
127
|
+
Format: `extensions_v{version}_{sequence}_{Description}`
|
|
128
|
+
|
|
129
|
+
| Example |
|
|
130
|
+
|---------|
|
|
131
|
+
| `extensions_v1.0.0_001_CreateOrders` |
|
|
132
|
+
| `extensions_v1.0.0_002_AddOrderItems` |
|
|
133
|
+
|
|
134
|
+
> **Note**: Use `/efcore:migration` which calls MCP `suggest_migration` for automatic naming.
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## 6. CODE CONVENTIONS
|
|
139
|
+
|
|
140
|
+
### Naming
|
|
141
|
+
| Type | Pattern | Example |
|
|
142
|
+
|------|---------|---------|
|
|
143
|
+
| Entity | Singular PascalCase | `Order`, `Product` |
|
|
144
|
+
| Command | `{Action}{Entity}Command` | `CreateOrderCommand` |
|
|
145
|
+
| Query | `Get{Entity}Query` | `GetOrderByIdQuery` |
|
|
146
|
+
| Handler | `{Command/Query}Handler` | `CreateOrderCommandHandler` |
|
|
147
|
+
| DTO | `{Entity}Dto` | `OrderDto`, `OrderResponseDto` |
|
|
148
|
+
|
|
149
|
+
### Backend Patterns
|
|
150
|
+
- **CQRS**: Commands/Queries in Application layer
|
|
151
|
+
- **Repository**: Interfaces in Application, impl in Infrastructure
|
|
152
|
+
- **DI**: Register in each layer's `DependencyInjection.cs`
|
|
153
|
+
- **Domain Events**: Raise in Domain, handle in Application
|
|
154
|
+
|
|
155
|
+
### Frontend Patterns
|
|
156
|
+
- **Feature-based structure**: Group by feature
|
|
157
|
+
- **Custom hooks**: `use*.ts` for logic
|
|
158
|
+
- **API layer**: Centralized in `services/api/`
|
|
159
|
+
- **Placeholder pages**: Use `UnderDevelopment` component for WIP sections
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## 7. COMMANDS
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
# Build & Run
|
|
167
|
+
dotnet build
|
|
168
|
+
cd src/{{ProjectName}}.Api && dotnet run
|
|
169
|
+
cd web/{{ProjectNameLower}}-web && npm run dev
|
|
170
|
+
|
|
171
|
+
# Tests (MANDATORY before commit)
|
|
172
|
+
dotnet test tests/{{ProjectName}}.Tests.Unit
|
|
173
|
+
cd web/{{ProjectNameLower}}-web && npm test
|
|
174
|
+
|
|
175
|
+
# EF Core
|
|
176
|
+
/efcore:migration <Name> # Create migration (auto -o)
|
|
177
|
+
/efcore:db-deploy # Apply migrations
|
|
178
|
+
/efcore:db-status # Check status
|
|
179
|
+
|
|
180
|
+
# GitFlow
|
|
181
|
+
/gitflow commit # Commit with validation
|
|
182
|
+
/gitflow pr # Create PR
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## 8. API MANAGEMENT
|
|
188
|
+
|
|
189
|
+
Claude MAY start/stop API as needed.
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
# Start (background)
|
|
193
|
+
cd src/{{ProjectName}}.Api && dotnet run &
|
|
194
|
+
|
|
195
|
+
# Stop
|
|
196
|
+
taskkill /F /IM {{ProjectName}}.Api.exe 2>nul || pkill -f {{ProjectName}}.Api
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
**Rules**: Stop API when done. Stop before rebuild if files locked. Restart after code changes.
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## 9. SECURITY RULES
|
|
204
|
+
|
|
205
|
+
### GUID Generation (MANDATORY)
|
|
206
|
+
```powershell
|
|
207
|
+
# ALWAYS generate via PowerShell
|
|
208
|
+
[guid]::NewGuid().ToString()
|
|
209
|
+
1..10 | ForEach-Object { [guid]::NewGuid().ToString() } # Multiple
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
**FORBIDDEN patterns**:
|
|
213
|
+
- Repeated: `a1a1a1a1-b1b1-...`
|
|
214
|
+
- Sequential: `00000001-0001-...`
|
|
215
|
+
- Predictable: `a1b2c3d4-1111-2222-...`
|
|
216
|
+
|
|
217
|
+
### Why
|
|
218
|
+
- Predictable GUIDs enable enumeration attacks
|
|
219
|
+
- OWASP requires cryptographically random identifiers
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## 10. ENTITY HOOKS
|
|
224
|
+
|
|
225
|
+
| Interface | When | Use Case |
|
|
226
|
+
|-----------|------|----------|
|
|
227
|
+
| `IBeforeCreate<T>` | Pre-create | Validation |
|
|
228
|
+
| `IAfterCreate<T>` | Post-commit | Notifications |
|
|
229
|
+
| `IBeforeUpdate<T>` | Pre-update | Validation |
|
|
230
|
+
| `IAfterUpdate<T>` | Post-commit | Notifications |
|
|
231
|
+
| `IBeforeDelete<T>` | Pre-delete | Can cancel |
|
|
232
|
+
| `IAfterDelete<T>` | Post-commit | Cleanup |
|
|
233
|
+
|
|
234
|
+
```csharp
|
|
235
|
+
// Register in DependencyInjection.cs
|
|
236
|
+
services.AddScoped<IAfterCreate<Order>, OrderCreatedNotificationHook>();
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## 11. LOGGING
|
|
242
|
+
|
|
243
|
+
### Backend (Serilog)
|
|
244
|
+
| Level | Usage |
|
|
245
|
+
|-------|-------|
|
|
246
|
+
| Critical | Security breaches, unauthorized access |
|
|
247
|
+
| Error | Unhandled exceptions |
|
|
248
|
+
| Warning | Validation failures |
|
|
249
|
+
| Information | HTTP requests, user actions |
|
|
250
|
+
| Debug | SQL queries (dev only) |
|
|
251
|
+
|
|
252
|
+
### Frontend
|
|
253
|
+
```typescript
|
|
254
|
+
import { logService } from '@/services/logging/logService';
|
|
255
|
+
logService.logError(error, 'ComponentName');
|
|
256
|
+
logService.logCritical(error, 'ComponentName'); // Immediate send
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
## 12. TESTING (MANDATORY)
|
|
262
|
+
|
|
263
|
+
### Rule
|
|
264
|
+
**Every new feature, entity, service, or utility MUST have corresponding unit tests.** No pull request should be merged without tests.
|
|
265
|
+
|
|
266
|
+
### Backend Tests (xUnit v3 + FluentAssertions 8.x + Moq)
|
|
267
|
+
|
|
268
|
+
| Layer | What to Test | Location |
|
|
269
|
+
|-------|-------------|----------|
|
|
270
|
+
| Domain | Entity creation, validation, state transitions | `tests/{{ProjectName}}.Tests.Unit/Domain/` |
|
|
271
|
+
| Application | Behaviors, mappings, DTOs | `tests/{{ProjectName}}.Tests.Unit/Application/` |
|
|
272
|
+
| Infrastructure | Services (JWT, FileStorage, etc.) | `tests/{{ProjectName}}.Tests.Unit/Services/` |
|
|
273
|
+
| API | Controller responses, authorization | `tests/{{ProjectName}}.Tests.Unit/Controllers/` |
|
|
274
|
+
|
|
275
|
+
```bash
|
|
276
|
+
dotnet test tests/{{ProjectName}}.Tests.Unit
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
**Conventions:**
|
|
280
|
+
- Test file naming: `{ClassName}Tests.cs`
|
|
281
|
+
- Use factory methods (private constructors) - NEVER `new Entity { ... }`
|
|
282
|
+
- Use `Guid.NewGuid()` for all Guid parameters - NEVER hardcode or use strings
|
|
283
|
+
|
|
284
|
+
**FluentAssertions 8.x Gotchas:**
|
|
285
|
+
- DateTime: `BeOnOrAfter()` / `BeBefore()` / `BeCloseTo(expected, TimeSpan.FromSeconds(5))` - NOT `BeGreaterThan()`
|
|
286
|
+
- Collections: `HaveCount(n)` - NOT `HaveLength(n)`
|
|
287
|
+
- String length: `content.Length.Should().BeGreaterThan(n)` - NOT `HaveLengthGreaterThan()`
|
|
288
|
+
- Nullable: use `.Value` explicitly
|
|
289
|
+
- `WithMessage("*keyword*")` wildcard OK - but do NOT pass `StringComparison` parameter
|
|
290
|
+
|
|
291
|
+
**Domain Entity Test Pattern:**
|
|
292
|
+
```csharp
|
|
293
|
+
[Fact]
|
|
294
|
+
public void Create_WithValidData_SetsAllProperties()
|
|
295
|
+
{
|
|
296
|
+
var before = DateTime.UtcNow;
|
|
297
|
+
var entity = Entity.Create("name", "value");
|
|
298
|
+
entity.Name.Should().Be("name");
|
|
299
|
+
entity.CreatedAt.Should().BeOnOrAfter(before);
|
|
300
|
+
entity.Id.Should().NotBeEmpty();
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
[Fact]
|
|
304
|
+
public void Create_WithEmptyName_ThrowsDomainException()
|
|
305
|
+
{
|
|
306
|
+
var act = () => Entity.Create("", "value");
|
|
307
|
+
act.Should().Throw<DomainException>().WithMessage("*name*");
|
|
308
|
+
}
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### Frontend Tests (Vitest + Testing Library)
|
|
312
|
+
|
|
313
|
+
| Category | What to Test | Location |
|
|
314
|
+
|----------|-------------|----------|
|
|
315
|
+
| Utils | Pure functions | `web/{{ProjectNameLower}}-web/tests/utils/` |
|
|
316
|
+
| Services | Business logic | `web/{{ProjectNameLower}}-web/tests/services/` |
|
|
317
|
+
| Hooks | Custom hooks | `web/{{ProjectNameLower}}-web/tests/hooks/` |
|
|
318
|
+
| Components | UI with interactions | `web/{{ProjectNameLower}}-web/tests/components/` |
|
|
319
|
+
|
|
320
|
+
```bash
|
|
321
|
+
cd web/{{ProjectNameLower}}-web && npm test
|
|
322
|
+
cd web/{{ProjectNameLower}}-web && npm run test:watch
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
**Conventions:**
|
|
326
|
+
- Test file naming: `{fileName}.test.ts` or `{fileName}.test.tsx`
|
|
327
|
+
- Tests in `web/{{ProjectNameLower}}-web/tests/` (NOT in `src/`)
|
|
328
|
+
- Import with `@/` alias
|
|
329
|
+
- Use `vi.mock()` for external dependencies
|
|
330
|
+
|
|
331
|
+
### When to Write Tests
|
|
332
|
+
|
|
333
|
+
| Trigger | Action |
|
|
334
|
+
|---------|--------|
|
|
335
|
+
| New domain entity | Add `{Entity}Tests.cs` with factory, validation, state machine tests |
|
|
336
|
+
| New utility function | Add `{util}.test.ts` covering edge cases |
|
|
337
|
+
| New service | Add `{Service}Tests.cs` or `{service}.test.ts` with mocked dependencies |
|
|
338
|
+
| Bug fix | Add regression test that reproduces the bug BEFORE fixing |
|
|
339
|
+
| New API endpoint | Add controller test with auth and response validation |
|