@mohantn/gate-keeper 2.1.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/.github/instructions/dotnet-api-integration.instructions.md +416 -0
- package/.github/instructions/dotnet-development.instructions.md +353 -0
- package/.github/instructions/dotnet-testing.instructions.md +406 -0
- package/.github/instructions/gate-keeper.instructions.md +91 -0
- package/.github/instructions/react-development.instructions.md +315 -0
- package/.github/instructions/react-testing-optimization.instructions.md +373 -0
- package/.github/instructions/uiux.instructions.md +261 -0
- package/LICENSE +21 -0
- package/README.md +181 -0
- package/dist/analyzer/coverage-analyzer.d.ts +126 -0
- package/dist/analyzer/coverage-analyzer.d.ts.map +1 -0
- package/dist/analyzer/coverage-analyzer.js +633 -0
- package/dist/analyzer/coverage-analyzer.js.map +1 -0
- package/dist/analyzer/csharp-analyzer.d.ts +28 -0
- package/dist/analyzer/csharp-analyzer.d.ts.map +1 -0
- package/dist/analyzer/csharp-analyzer.js +437 -0
- package/dist/analyzer/csharp-analyzer.js.map +1 -0
- package/dist/analyzer/pattern-detector.d.ts +5 -0
- package/dist/analyzer/pattern-detector.d.ts.map +1 -0
- package/dist/analyzer/pattern-detector.js +74 -0
- package/dist/analyzer/pattern-detector.js.map +1 -0
- package/dist/analyzer/refactoring-advisor.d.ts +7 -0
- package/dist/analyzer/refactoring-advisor.d.ts.map +1 -0
- package/dist/analyzer/refactoring-advisor.js +280 -0
- package/dist/analyzer/refactoring-advisor.js.map +1 -0
- package/dist/analyzer/sonar-eslint-runner.d.ts +3 -0
- package/dist/analyzer/sonar-eslint-runner.d.ts.map +1 -0
- package/dist/analyzer/sonar-eslint-runner.js +136 -0
- package/dist/analyzer/sonar-eslint-runner.js.map +1 -0
- package/dist/analyzer/sonar-rule-map.d.ts +19 -0
- package/dist/analyzer/sonar-rule-map.d.ts.map +1 -0
- package/dist/analyzer/sonar-rule-map.js +67 -0
- package/dist/analyzer/sonar-rule-map.js.map +1 -0
- package/dist/analyzer/string-analyzer.d.ts +27 -0
- package/dist/analyzer/string-analyzer.d.ts.map +1 -0
- package/dist/analyzer/string-analyzer.js +274 -0
- package/dist/analyzer/string-analyzer.js.map +1 -0
- package/dist/analyzer/typescript-analyzer.d.ts +27 -0
- package/dist/analyzer/typescript-analyzer.d.ts.map +1 -0
- package/dist/analyzer/typescript-analyzer.js +437 -0
- package/dist/analyzer/typescript-analyzer.js.map +1 -0
- package/dist/analyzer/universal-analyzer.d.ts +10 -0
- package/dist/analyzer/universal-analyzer.d.ts.map +1 -0
- package/dist/analyzer/universal-analyzer.js +155 -0
- package/dist/analyzer/universal-analyzer.js.map +1 -0
- package/dist/cache/quality-cache.d.ts +119 -0
- package/dist/cache/quality-cache.d.ts.map +1 -0
- package/dist/cache/quality-cache.js +130 -0
- package/dist/cache/quality-cache.js.map +1 -0
- package/dist/cache/sqlite-cache.d.ts +43 -0
- package/dist/cache/sqlite-cache.d.ts.map +1 -0
- package/dist/cache/sqlite-cache.js +346 -0
- package/dist/cache/sqlite-cache.js.map +1 -0
- package/dist/cli/query-repl.d.ts +37 -0
- package/dist/cli/query-repl.d.ts.map +1 -0
- package/dist/cli/query-repl.js +298 -0
- package/dist/cli/query-repl.js.map +1 -0
- package/dist/cli/repl-algorithms.d.ts +49 -0
- package/dist/cli/repl-algorithms.d.ts.map +1 -0
- package/dist/cli/repl-algorithms.js +147 -0
- package/dist/cli/repl-algorithms.js.map +1 -0
- package/dist/cli/setup-core.d.ts +38 -0
- package/dist/cli/setup-core.d.ts.map +1 -0
- package/dist/cli/setup-core.js +427 -0
- package/dist/cli/setup-core.js.map +1 -0
- package/dist/cli/setup.d.ts +25 -0
- package/dist/cli/setup.d.ts.map +1 -0
- package/dist/cli/setup.js +159 -0
- package/dist/cli/setup.js.map +1 -0
- package/dist/cli-entry.d.ts +19 -0
- package/dist/cli-entry.d.ts.map +1 -0
- package/dist/cli-entry.js +178 -0
- package/dist/cli-entry.js.map +1 -0
- package/dist/daemon/watch-mode.d.ts +41 -0
- package/dist/daemon/watch-mode.d.ts.map +1 -0
- package/dist/daemon/watch-mode.js +163 -0
- package/dist/daemon/watch-mode.js.map +1 -0
- package/dist/daemon.d.ts +24 -0
- package/dist/daemon.d.ts.map +1 -0
- package/dist/daemon.js +357 -0
- package/dist/daemon.js.map +1 -0
- package/dist/github/app.d.ts +34 -0
- package/dist/github/app.d.ts.map +1 -0
- package/dist/github/app.js +261 -0
- package/dist/github/app.js.map +1 -0
- package/dist/github/commenter.d.ts +67 -0
- package/dist/github/commenter.d.ts.map +1 -0
- package/dist/github/commenter.js +155 -0
- package/dist/github/commenter.js.map +1 -0
- package/dist/graph/dependency-graph.d.ts +28 -0
- package/dist/graph/dependency-graph.d.ts.map +1 -0
- package/dist/graph/dependency-graph.js +198 -0
- package/dist/graph/dependency-graph.js.map +1 -0
- package/dist/graph/global-graph.d.ts +65 -0
- package/dist/graph/global-graph.d.ts.map +1 -0
- package/dist/graph/global-graph.js +153 -0
- package/dist/graph/global-graph.js.map +1 -0
- package/dist/graph/graph-algorithms.d.ts +90 -0
- package/dist/graph/graph-algorithms.d.ts.map +1 -0
- package/dist/graph/graph-algorithms.js +180 -0
- package/dist/graph/graph-algorithms.js.map +1 -0
- package/dist/graph/graph-export.d.ts +68 -0
- package/dist/graph/graph-export.d.ts.map +1 -0
- package/dist/graph/graph-export.js +264 -0
- package/dist/graph/graph-export.js.map +1 -0
- package/dist/graph/graph-report.d.ts +34 -0
- package/dist/graph/graph-report.d.ts.map +1 -0
- package/dist/graph/graph-report.js +136 -0
- package/dist/graph/graph-report.js.map +1 -0
- package/dist/graph/graph-summary.d.ts +68 -0
- package/dist/graph/graph-summary.d.ts.map +1 -0
- package/dist/graph/graph-summary.js +213 -0
- package/dist/graph/graph-summary.js.map +1 -0
- package/dist/graph/graphify-ignore.d.ts +32 -0
- package/dist/graph/graphify-ignore.d.ts.map +1 -0
- package/dist/graph/graphify-ignore.js +124 -0
- package/dist/graph/graphify-ignore.js.map +1 -0
- package/dist/graph/question-suggester.d.ts +30 -0
- package/dist/graph/question-suggester.d.ts.map +1 -0
- package/dist/graph/question-suggester.js +113 -0
- package/dist/graph/question-suggester.js.map +1 -0
- package/dist/graph/relationship-extractor.d.ts +40 -0
- package/dist/graph/relationship-extractor.d.ts.map +1 -0
- package/dist/graph/relationship-extractor.js +254 -0
- package/dist/graph/relationship-extractor.js.map +1 -0
- package/dist/graph/relationship-types.d.ts +24 -0
- package/dist/graph/relationship-types.d.ts.map +1 -0
- package/dist/graph/relationship-types.js +21 -0
- package/dist/graph/relationship-types.js.map +1 -0
- package/dist/graph/surprising-connections.d.ts +39 -0
- package/dist/graph/surprising-connections.d.ts.map +1 -0
- package/dist/graph/surprising-connections.js +127 -0
- package/dist/graph/surprising-connections.js.map +1 -0
- package/dist/hook-pre-tool-use.d.ts +14 -0
- package/dist/hook-pre-tool-use.d.ts.map +1 -0
- package/dist/hook-pre-tool-use.js +167 -0
- package/dist/hook-pre-tool-use.js.map +1 -0
- package/dist/hook-receiver.d.ts +29 -0
- package/dist/hook-receiver.d.ts.map +1 -0
- package/dist/hook-receiver.js +327 -0
- package/dist/hook-receiver.js.map +1 -0
- package/dist/hooks/git-hooks.d.ts +30 -0
- package/dist/hooks/git-hooks.d.ts.map +1 -0
- package/dist/hooks/git-hooks.js +179 -0
- package/dist/hooks/git-hooks.js.map +1 -0
- package/dist/mcp/cache-preload.d.ts +29 -0
- package/dist/mcp/cache-preload.d.ts.map +1 -0
- package/dist/mcp/cache-preload.js +103 -0
- package/dist/mcp/cache-preload.js.map +1 -0
- package/dist/mcp/handlers/analysis.d.ts +4 -0
- package/dist/mcp/handlers/analysis.d.ts.map +1 -0
- package/dist/mcp/handlers/analysis.js +196 -0
- package/dist/mcp/handlers/analysis.js.map +1 -0
- package/dist/mcp/handlers/context.d.ts +25 -0
- package/dist/mcp/handlers/context.d.ts.map +1 -0
- package/dist/mcp/handlers/context.js +382 -0
- package/dist/mcp/handlers/context.js.map +1 -0
- package/dist/mcp/handlers/graph-intelligence.d.ts +26 -0
- package/dist/mcp/handlers/graph-intelligence.d.ts.map +1 -0
- package/dist/mcp/handlers/graph-intelligence.js +371 -0
- package/dist/mcp/handlers/graph-intelligence.js.map +1 -0
- package/dist/mcp/handlers/graph-query.d.ts +25 -0
- package/dist/mcp/handlers/graph-query.d.ts.map +1 -0
- package/dist/mcp/handlers/graph-query.js +410 -0
- package/dist/mcp/handlers/graph-query.js.map +1 -0
- package/dist/mcp/handlers/graph.d.ts +5 -0
- package/dist/mcp/handlers/graph.d.ts.map +1 -0
- package/dist/mcp/handlers/graph.js +283 -0
- package/dist/mcp/handlers/graph.js.map +1 -0
- package/dist/mcp/handlers/impact-format.d.ts +9 -0
- package/dist/mcp/handlers/impact-format.d.ts.map +1 -0
- package/dist/mcp/handlers/impact-format.js +189 -0
- package/dist/mcp/handlers/impact-format.js.map +1 -0
- package/dist/mcp/handlers/impact.d.ts +4 -0
- package/dist/mcp/handlers/impact.d.ts.map +1 -0
- package/dist/mcp/handlers/impact.js +139 -0
- package/dist/mcp/handlers/impact.js.map +1 -0
- package/dist/mcp/handlers/improvement.d.ts +4 -0
- package/dist/mcp/handlers/improvement.d.ts.map +1 -0
- package/dist/mcp/handlers/improvement.js +136 -0
- package/dist/mcp/handlers/improvement.js.map +1 -0
- package/dist/mcp/handlers/index.d.ts +14 -0
- package/dist/mcp/handlers/index.d.ts.map +1 -0
- package/dist/mcp/handlers/index.js +36 -0
- package/dist/mcp/handlers/index.js.map +1 -0
- package/dist/mcp/handlers/platform-installer.d.ts +10 -0
- package/dist/mcp/handlers/platform-installer.d.ts.map +1 -0
- package/dist/mcp/handlers/platform-installer.js +168 -0
- package/dist/mcp/handlers/platform-installer.js.map +1 -0
- package/dist/mcp/handlers/pr-review.d.ts +33 -0
- package/dist/mcp/handlers/pr-review.d.ts.map +1 -0
- package/dist/mcp/handlers/pr-review.js +170 -0
- package/dist/mcp/handlers/pr-review.js.map +1 -0
- package/dist/mcp/handlers/shared.d.ts +20 -0
- package/dist/mcp/handlers/shared.d.ts.map +1 -0
- package/dist/mcp/handlers/shared.js +27 -0
- package/dist/mcp/handlers/shared.js.map +1 -0
- package/dist/mcp/handlers/types.d.ts +46 -0
- package/dist/mcp/handlers/types.d.ts.map +1 -0
- package/dist/mcp/handlers/types.js +3 -0
- package/dist/mcp/handlers/types.js.map +1 -0
- package/dist/mcp/helpers.d.ts +36 -0
- package/dist/mcp/helpers.d.ts.map +1 -0
- package/dist/mcp/helpers.js +199 -0
- package/dist/mcp/helpers.js.map +1 -0
- package/dist/mcp/installer.d.ts +22 -0
- package/dist/mcp/installer.d.ts.map +1 -0
- package/dist/mcp/installer.js +341 -0
- package/dist/mcp/installer.js.map +1 -0
- package/dist/mcp/server.d.ts +111 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +216 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/token-tracker.d.ts +47 -0
- package/dist/mcp/token-tracker.d.ts.map +1 -0
- package/dist/mcp/token-tracker.js +93 -0
- package/dist/mcp/token-tracker.js.map +1 -0
- package/dist/quality-loop/file-lock.d.ts +12 -0
- package/dist/quality-loop/file-lock.d.ts.map +1 -0
- package/dist/quality-loop/file-lock.js +38 -0
- package/dist/quality-loop/file-lock.js.map +1 -0
- package/dist/quality-loop/fix-worker.d.ts +44 -0
- package/dist/quality-loop/fix-worker.d.ts.map +1 -0
- package/dist/quality-loop/fix-worker.js +414 -0
- package/dist/quality-loop/fix-worker.js.map +1 -0
- package/dist/quality-loop/orchestrator.d.ts +137 -0
- package/dist/quality-loop/orchestrator.d.ts.map +1 -0
- package/dist/quality-loop/orchestrator.js +894 -0
- package/dist/quality-loop/orchestrator.js.map +1 -0
- package/dist/quality-loop/queue-manager.d.ts +45 -0
- package/dist/quality-loop/queue-manager.d.ts.map +1 -0
- package/dist/quality-loop/queue-manager.js +173 -0
- package/dist/quality-loop/queue-manager.js.map +1 -0
- package/dist/rating/rating-calculator.d.ts +15 -0
- package/dist/rating/rating-calculator.d.ts.map +1 -0
- package/dist/rating/rating-calculator.js +136 -0
- package/dist/rating/rating-calculator.js.map +1 -0
- package/dist/types/agent.d.ts +49 -0
- package/dist/types/agent.d.ts.map +1 -0
- package/dist/types/agent.js +7 -0
- package/dist/types/agent.js.map +1 -0
- package/dist/types.d.ts +156 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/util/fix-text.d.ts +7 -0
- package/dist/util/fix-text.d.ts.map +1 -0
- package/dist/util/fix-text.js +13 -0
- package/dist/util/fix-text.js.map +1 -0
- package/dist/viz/graph-viz.d.ts +40 -0
- package/dist/viz/graph-viz.d.ts.map +1 -0
- package/dist/viz/graph-viz.js +332 -0
- package/dist/viz/graph-viz.js.map +1 -0
- package/dist/viz/viz-helpers.d.ts +13 -0
- package/dist/viz/viz-helpers.d.ts.map +1 -0
- package/dist/viz/viz-helpers.js +134 -0
- package/dist/viz/viz-helpers.js.map +1 -0
- package/dist/viz/viz-routes.d.ts +28 -0
- package/dist/viz/viz-routes.d.ts.map +1 -0
- package/dist/viz/viz-routes.js +333 -0
- package/dist/viz/viz-routes.js.map +1 -0
- package/dist/viz/viz-scanner.d.ts +20 -0
- package/dist/viz/viz-scanner.d.ts.map +1 -0
- package/dist/viz/viz-scanner.js +241 -0
- package/dist/viz/viz-scanner.js.map +1 -0
- package/dist/viz/viz-server.d.ts +38 -0
- package/dist/viz/viz-server.d.ts.map +1 -0
- package/dist/viz/viz-server.js +240 -0
- package/dist/viz/viz-server.js.map +1 -0
- package/package.json +89 -0
- package/scripts/postinstall.js +28 -0
- package/scripts/setup.sh +113 -0
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
# .NET Core 8 API & Integration Instructions
|
|
2
|
+
|
|
3
|
+
**Scope**: DI, Controllers, Auth, Validation | **Version**: 2.0 | **Tags**: `api`, `authentication`, `dependency-injection`
|
|
4
|
+
|
|
5
|
+
**Related Files**: See [dotnet-development.instructions.md](./dotnet-development.instructions.md) for core patterns, [dotnet-testing.instructions.md](./dotnet-testing.instructions.md) for testing
|
|
6
|
+
|
|
7
|
+
## Dependency Injection & Configuration
|
|
8
|
+
|
|
9
|
+
### DI Registration in Startup.ConfigureServices
|
|
10
|
+
Register services with appropriate lifetimes: **Transient** (new instance each time), **Scoped** (per request), **Singleton** (application lifetime).
|
|
11
|
+
|
|
12
|
+
```csharp
|
|
13
|
+
public class Startup {
|
|
14
|
+
public void ConfigureServices(IServiceCollection services) {
|
|
15
|
+
// Configuration & Options Pattern
|
|
16
|
+
services.AddOptions().Configure<Settings>(options => Configuration.Bind(options));
|
|
17
|
+
|
|
18
|
+
// DbContext (scoped per request)
|
|
19
|
+
services.AddDbContext<StoreMediaDbContext>();
|
|
20
|
+
services.AddServiceBundle(Configuration);
|
|
21
|
+
|
|
22
|
+
// Repositories & Unit of Work (scoped)
|
|
23
|
+
services.AddScoped<IMediaElementRepository, MediaElementRepository>();
|
|
24
|
+
services.AddScoped<IUnitOfWork, UnitOfWork>();
|
|
25
|
+
|
|
26
|
+
// Query Handlers & Commands (scoped)
|
|
27
|
+
services.AddScoped<GetPresetElementsHandler>();
|
|
28
|
+
|
|
29
|
+
// Mappers (scoped or transient)
|
|
30
|
+
services.AddScoped<ProductMapper>();
|
|
31
|
+
|
|
32
|
+
// Services (scoped per business logic)
|
|
33
|
+
services.AddScoped<IProductService, ProductService>();
|
|
34
|
+
|
|
35
|
+
// HTTP & Middleware
|
|
36
|
+
services.AddHttpContextAccessor();
|
|
37
|
+
services.AddMemoryCache();
|
|
38
|
+
services.AddMediatR(typeof(Program));
|
|
39
|
+
|
|
40
|
+
// Controllers with JSON options
|
|
41
|
+
services.AddControllers()
|
|
42
|
+
.AddJsonOptions(opts => {
|
|
43
|
+
opts.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
|
|
44
|
+
opts.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles;
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Settings Class & IOptions Pattern
|
|
51
|
+
```csharp
|
|
52
|
+
// Configuration/Settings.cs
|
|
53
|
+
public class Settings {
|
|
54
|
+
public DatabaseSettings Database { get; set; }
|
|
55
|
+
public PubSubSettings PubSub { get; set; }
|
|
56
|
+
public string BaseUri { get; set; }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// In appsettings.json
|
|
60
|
+
{
|
|
61
|
+
"Settings": {
|
|
62
|
+
"Database": { "ConnectionString": "..." },
|
|
63
|
+
"BaseUri": "https://api.example.com"
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Usage in Service
|
|
68
|
+
public class ProductService {
|
|
69
|
+
private readonly Settings _settings;
|
|
70
|
+
|
|
71
|
+
public ProductService(IOptions<Settings> options) {
|
|
72
|
+
_settings = options.Value;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Validation
|
|
78
|
+
|
|
79
|
+
### FluentValidation Pattern
|
|
80
|
+
```csharp
|
|
81
|
+
public class CreateProductCommandValidator : AbstractValidator<CreateProductCommand> {
|
|
82
|
+
private readonly StoreMediaDbContext _dbContext;
|
|
83
|
+
|
|
84
|
+
public CreateProductCommandValidator(StoreMediaDbContext dbContext) {
|
|
85
|
+
_dbContext = dbContext;
|
|
86
|
+
|
|
87
|
+
RuleFor(x => x.Name)
|
|
88
|
+
.NotEmpty().WithMessage("Name is required")
|
|
89
|
+
.MaximumLength(255).WithMessage("Name must not exceed 255 characters");
|
|
90
|
+
|
|
91
|
+
RuleFor(x => x.Price)
|
|
92
|
+
.GreaterThan(0).WithMessage("Price must be greater than zero");
|
|
93
|
+
|
|
94
|
+
// Async rule: check database uniqueness
|
|
95
|
+
RuleFor(x => x.Name)
|
|
96
|
+
.MustAsync(async (name, ct) => {
|
|
97
|
+
var exists = await _dbContext.Products.AnyAsync(p => p.Name == name, ct);
|
|
98
|
+
return !exists;
|
|
99
|
+
})
|
|
100
|
+
.WithMessage("Product name already exists");
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Register in DI
|
|
105
|
+
services.AddScoped<IValidator<CreateProductCommand>, CreateProductCommandValidator>();
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## API Controllers
|
|
109
|
+
|
|
110
|
+
### Controller Pattern with MediatR
|
|
111
|
+
```csharp
|
|
112
|
+
[ApiController]
|
|
113
|
+
[Route("v2/products")]
|
|
114
|
+
public class ProductController : ControllerBase {
|
|
115
|
+
private readonly IMediator _mediator;
|
|
116
|
+
|
|
117
|
+
public ProductController(IMediator mediator) {
|
|
118
|
+
_mediator = mediator;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/// <summary>
|
|
122
|
+
/// Creates a new product.
|
|
123
|
+
/// </summary>
|
|
124
|
+
[HttpPost]
|
|
125
|
+
[ScopedPermissions(Permissions.ProductAdmin)]
|
|
126
|
+
[ProducesResponseType(typeof(ProductDto), 201)]
|
|
127
|
+
[ProducesResponseType(typeof(ProblemDetails), 400)]
|
|
128
|
+
public async Task<IActionResult> CreateProductAsync([FromBody] CreateProductCommand command) {
|
|
129
|
+
if (!ModelState.IsValid) return BadRequest(ModelState);
|
|
130
|
+
|
|
131
|
+
var result = await _mediator.Send(command, HttpContext.RequestAborted);
|
|
132
|
+
return CreatedAtAction(nameof(GetProductAsync), new { id = result.Id }, result);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/// <summary>
|
|
136
|
+
/// Gets a product by ID.
|
|
137
|
+
/// </summary>
|
|
138
|
+
[HttpGet("{id}")]
|
|
139
|
+
[ScopedPermissions(Permissions.ProductView)]
|
|
140
|
+
[ProducesResponseType(typeof(ProductDto), 200)]
|
|
141
|
+
[ProducesResponseType(typeof(ProblemDetails), 404)]
|
|
142
|
+
public async Task<ActionResult<ProductDto>> GetProductAsync(string id) {
|
|
143
|
+
var query = new GetProductQuery { Id = id };
|
|
144
|
+
return Ok(await _mediator.Send(query, HttpContext.RequestAborted));
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Global Error Handling (ProblemDetails)
|
|
150
|
+
```csharp
|
|
151
|
+
// Startup.ConfigureServices
|
|
152
|
+
services.AddProblemDetails(options => {
|
|
153
|
+
options.IncludeExceptionDetails = (_, _) => _environment.IsDevelopment();
|
|
154
|
+
options.Map<NotFoundException>(e => new ProblemDetails {
|
|
155
|
+
Title = "Resource Not Found",
|
|
156
|
+
Status = StatusCodes.Status404NotFound,
|
|
157
|
+
Detail = e.Message
|
|
158
|
+
});
|
|
159
|
+
options.Map<ValidationException>(e => new ProblemDetails {
|
|
160
|
+
Title = "Validation Error",
|
|
161
|
+
Status = StatusCodes.Status400BadRequest,
|
|
162
|
+
Detail = string.Join("; ", e.Errors.Select(x => x.ErrorMessage))
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// Startup.Configure
|
|
167
|
+
app.UseProblemDetails();
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Logging & Error Handling
|
|
171
|
+
|
|
172
|
+
### Structured Logging with Serilog
|
|
173
|
+
```csharp
|
|
174
|
+
// Program.cs
|
|
175
|
+
public static IWebHostBuilder CreateHostBuilder(string[] args) {
|
|
176
|
+
return new WebHostBuilder()
|
|
177
|
+
.UseSerilog((context, loggerConfig) => {
|
|
178
|
+
loggerConfig
|
|
179
|
+
.MinimumLevel.Information()
|
|
180
|
+
.WriteTo.Console()
|
|
181
|
+
.Enrich.FromLogContext()
|
|
182
|
+
.Enrich.WithProperty("ApplicationName", "StoreMediaApi");
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Usage in Handlers
|
|
187
|
+
public class CreateProductCommandHandler : IRequestHandler<CreateProductCommand, ProductDto> {
|
|
188
|
+
private readonly ILogger<CreateProductCommandHandler> _logger;
|
|
189
|
+
|
|
190
|
+
public async Task<ProductDto> Handle(CreateProductCommand request, CancellationToken ct) {
|
|
191
|
+
_logger.LogInformation("Creating product: {ProductName}", request.Name);
|
|
192
|
+
|
|
193
|
+
try {
|
|
194
|
+
var product = Product.Create(request.Name, request.Price, request.RetailerId);
|
|
195
|
+
await _unitOfWork.Products.AddAsync(product, ct);
|
|
196
|
+
await _unitOfWork.SaveChangesAsync(ct);
|
|
197
|
+
|
|
198
|
+
_logger.LogInformation("Product created: {ProductId}", product.Id);
|
|
199
|
+
return _mapper.MapToDto(product);
|
|
200
|
+
} catch (Exception ex) {
|
|
201
|
+
_logger.LogError(ex, "Error creating product: {ErrorMessage}", ex.Message);
|
|
202
|
+
throw;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### Custom Error Codes for Monitoring
|
|
209
|
+
```csharp
|
|
210
|
+
public static class ErrorCodes {
|
|
211
|
+
public const string OutboxUpdateFailure = "SMA-OutboxUpdateFailure";
|
|
212
|
+
public const string ServiceCallFailure = "SMA-ServiceToServiceCallFailure";
|
|
213
|
+
public const string ValidationFailure = "SMA-ValidationFailure";
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Usage
|
|
217
|
+
_logger.LogError("Failed: {ErrorCode} for {Entity}", ErrorCodes.OutboxUpdateFailure, "Activation");
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
## Authorization & Authentication
|
|
221
|
+
|
|
222
|
+
### JWT & Permissions via ServiceBundle
|
|
223
|
+
```csharp
|
|
224
|
+
// Startup.ConfigureServices
|
|
225
|
+
services.AddServiceBundle(Configuration); // Includes auth middleware
|
|
226
|
+
services.AddScoped<IPermissionsManagerV4Client, PermissionsManagerV4Client>();
|
|
227
|
+
|
|
228
|
+
// Controller: Use [ScopedPermissions] attribute
|
|
229
|
+
[HttpPost]
|
|
230
|
+
[ScopedPermissions(Permissions.StoreMediaAdmin)]
|
|
231
|
+
public async Task<IActionResult> CreateAsync([FromBody] CreateCommand cmd) {
|
|
232
|
+
return await _mediator.Send(cmd);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Extract JWT from HttpContext
|
|
236
|
+
public static class HttpContextExtensions {
|
|
237
|
+
public static string ExtractJwt(this IHttpContextAccessor accessor) {
|
|
238
|
+
var authHeader = accessor.HttpContext?.Request.Headers["Authorization"].FirstOrDefault();
|
|
239
|
+
return authHeader?.Replace("Bearer ", "") ?? string.Empty;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Custom Authorization Filter
|
|
244
|
+
[ExcludeFromCodeCoverage]
|
|
245
|
+
public class HangfireAuthorizationFilter : IDashboardAuthorizationFilter {
|
|
246
|
+
private readonly IPermissionsManagerV4Client _permissionsManager;
|
|
247
|
+
|
|
248
|
+
public HangfireAuthorizationFilter(IPermissionsManagerV4Client permissionsManager) {
|
|
249
|
+
_permissionsManager = permissionsManager;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
public bool Authorize(DashboardContext context) {
|
|
253
|
+
var httpContext = context.GetHttpContext();
|
|
254
|
+
var jwt = httpContext.Request.Headers["Authorization"].FirstOrDefault()?.Replace("Bearer ", "");
|
|
255
|
+
|
|
256
|
+
if (string.IsNullOrEmpty(jwt)) return false;
|
|
257
|
+
|
|
258
|
+
var permissions = _permissionsManager.GetPermissions(jwt).Result;
|
|
259
|
+
return permissions.Contains(Permissions.HangfireDashboard);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
## Security Best Practices
|
|
265
|
+
|
|
266
|
+
### Input Validation & SQL Injection Prevention
|
|
267
|
+
```csharp
|
|
268
|
+
// ✅ GOOD: Input validation via FluentValidation
|
|
269
|
+
public class CreateProductCommandValidator : AbstractValidator<CreateProductCommand> {
|
|
270
|
+
public CreateProductCommandValidator() {
|
|
271
|
+
RuleFor(x => x.Name)
|
|
272
|
+
.NotEmpty()
|
|
273
|
+
.MaximumLength(255)
|
|
274
|
+
.Matches(@"^[a-zA-Z0-9\s\-_]+$").WithMessage("Invalid characters");
|
|
275
|
+
|
|
276
|
+
RuleFor(x => x.Price)
|
|
277
|
+
.GreaterThan(0)
|
|
278
|
+
.LessThan(1000000);
|
|
279
|
+
|
|
280
|
+
RuleFor(x => x.Email)
|
|
281
|
+
.EmailAddress().When(x => !string.IsNullOrEmpty(x.Email));
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// ✅ GOOD: Parameterized queries (EF Core default)
|
|
286
|
+
var products = await _dbContext.Products
|
|
287
|
+
.Where(p => p.RetailerId == retailerId)
|
|
288
|
+
.ToListAsync();
|
|
289
|
+
|
|
290
|
+
// ❌ BAD: Raw SQL with string interpolation (SQL injection risk)
|
|
291
|
+
var products = await _dbContext.Products
|
|
292
|
+
.FromSqlRaw($"SELECT * FROM products WHERE retailer_id = '{retailerId}'")
|
|
293
|
+
.ToListAsync();
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### Secrets Management
|
|
297
|
+
```csharp
|
|
298
|
+
// ✅ GOOD: Secrets via configuration, never hardcoded
|
|
299
|
+
public class Startup {
|
|
300
|
+
public void ConfigureServices(IServiceCollection services) {
|
|
301
|
+
var connectionString = Configuration.GetConnectionString("DefaultConnection");
|
|
302
|
+
services.AddDbContext<AppDbContext>(options =>
|
|
303
|
+
options.UseNpgsql(connectionString));
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// ✅ GOOD: Azure Key Vault integration
|
|
308
|
+
public class Program {
|
|
309
|
+
public static IHostBuilder CreateHostBuilder(string[] args) =>
|
|
310
|
+
Host.CreateDefaultBuilder(args)
|
|
311
|
+
.ConfigureAppConfiguration((context, config) => {
|
|
312
|
+
if (context.HostingEnvironment.IsProduction()) {
|
|
313
|
+
var builtConfig = config.Build();
|
|
314
|
+
config.AddAzureKeyVault(builtConfig["KeyVaultUrl"]);
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### HTTPS & CORS
|
|
321
|
+
```csharp
|
|
322
|
+
// ✅ GOOD: HTTPS & CORS restrictions
|
|
323
|
+
public class Startup {
|
|
324
|
+
public void ConfigureServices(IServiceCollection services) {
|
|
325
|
+
services.AddCors(options => {
|
|
326
|
+
options.AddPolicy("AllowedOrigins", builder => {
|
|
327
|
+
builder.WithOrigins("https://trusted-domain.com")
|
|
328
|
+
.AllowAnyMethod()
|
|
329
|
+
.AllowAnyHeader()
|
|
330
|
+
.AllowCredentials();
|
|
331
|
+
});
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
services.AddHttpsRedirection(options => {
|
|
335
|
+
options.HttpsPort = 443;
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
public void Configure(IApplicationBuilder app) {
|
|
340
|
+
app.UseHttpsRedirection();
|
|
341
|
+
app.UseCors("AllowedOrigins");
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
### Audit Logging
|
|
347
|
+
```csharp
|
|
348
|
+
// ✅ GOOD: Audit logging for sensitive operations
|
|
349
|
+
_logger.LogInformation(
|
|
350
|
+
"Product created by user {UserId}: {ProductId}, Name: {ProductName}",
|
|
351
|
+
userId,
|
|
352
|
+
product.Id,
|
|
353
|
+
product.Name);
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
## XML Documentation & Swagger
|
|
357
|
+
|
|
358
|
+
### XML Comments for API Documentation
|
|
359
|
+
```csharp
|
|
360
|
+
/// <summary>
|
|
361
|
+
/// Creates a new product in the system.
|
|
362
|
+
/// </summary>
|
|
363
|
+
/// <param name="command">The product creation command with required fields.</param>
|
|
364
|
+
/// <returns>The created product with generated ID.</returns>
|
|
365
|
+
/// <response code="201">Product created successfully.</response>
|
|
366
|
+
/// <response code="400">Invalid input data.</response>
|
|
367
|
+
/// <response code="404">Related entity not found.</response>
|
|
368
|
+
[HttpPost]
|
|
369
|
+
[ProducesResponseType(typeof(ProductDto), 201)]
|
|
370
|
+
[ProducesResponseType(typeof(ProblemDetails), 400)]
|
|
371
|
+
public async Task<IActionResult> CreateProductAsync([FromBody] CreateProductCommand command) {
|
|
372
|
+
// Implementation
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Startup: Enable Swagger XML documentation
|
|
376
|
+
services.AddSwaggerGen(options => {
|
|
377
|
+
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
|
|
378
|
+
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
|
|
379
|
+
options.IncludeXmlComments(xmlPath);
|
|
380
|
+
});
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
## Quick Checklist ✅
|
|
384
|
+
|
|
385
|
+
- DI registrations use correct lifetime (Transient/Scoped/Singleton)
|
|
386
|
+
- IOptions<T> pattern for configuration
|
|
387
|
+
- FluentValidation for all commands/queries
|
|
388
|
+
- MediatR for CQRS handlers
|
|
389
|
+
- [ScopedPermissions] on all endpoints
|
|
390
|
+
- Structured logging with Serilog
|
|
391
|
+
- ProblemDetails for error responses
|
|
392
|
+
- XML documentation on public APIs
|
|
393
|
+
- Secrets via configuration/Key Vault
|
|
394
|
+
- HTTPS enforced in production
|
|
395
|
+
- CORS restricted to trusted origins
|
|
396
|
+
- Audit logging for sensitive operations
|
|
397
|
+
|
|
398
|
+
## Common Mistakes ❌
|
|
399
|
+
|
|
400
|
+
- Hardcoding secrets/connection strings
|
|
401
|
+
- Missing input validation
|
|
402
|
+
- Not using parameterized queries
|
|
403
|
+
- Business logic in controllers
|
|
404
|
+
- No authorization checks
|
|
405
|
+
- Missing structured logging
|
|
406
|
+
- Raw exceptions exposed to clients
|
|
407
|
+
- CORS allowing all origins
|
|
408
|
+
- No audit trail for sensitive operations
|
|
409
|
+
|
|
410
|
+
## Resources
|
|
411
|
+
|
|
412
|
+
- [Microsoft ASP.NET Core Docs](https://docs.microsoft.com/aspnet/core/)
|
|
413
|
+
- [FluentValidation](https://fluentvalidation.net/)
|
|
414
|
+
- [MediatR](https://github.com/jbogard/MediatR)
|
|
415
|
+
- [Serilog](https://serilog.net/)
|
|
416
|
+
- [RFC 7807 Problem Details](https://tools.ietf.org/html/rfc7807)
|