@mohantn/gate-keeper 2.2.2 → 2.2.4

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/scripts/setup.sh CHANGED
@@ -1,9 +1,37 @@
1
1
  #!/usr/bin/env bash
2
2
  set -e
3
3
 
4
+ MODE="${1:-both}"
5
+
4
6
  GATE_KEEPER_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
5
7
  DAEMON_PID_FILE="$HOME/.gate-keeper/daemon.pid"
6
8
 
9
+ # Validate mode
10
+ if [[ "$MODE" != "claude" && "$MODE" != "copilot" && "$MODE" != "both" ]]; then
11
+ echo "[gate-keeper] Unknown option: $MODE"
12
+ echo " Usage: gate-keeper setup [claude|copilot]"
13
+ echo " claude - Configure for Claude Code (writes hooks to ~/.claude/settings.json)"
14
+ echo " copilot - Configure for GitHub Copilot / VS Code (prints .vscode/ setup)"
15
+ echo " (none) - Configure for both Claude Code and Copilot"
16
+ exit 1
17
+ fi
18
+
19
+ if [[ "$MODE" == "both" ]]; then
20
+ echo "[gate-keeper] Setting up for Claude Code and Copilot"
21
+ elif [[ "$MODE" == "claude" ]]; then
22
+ echo "[gate-keeper] Setting up for Claude Code only"
23
+ else
24
+ echo "[gate-keeper] Setting up for Copilot / VS Code only"
25
+ fi
26
+
27
+ # Detect installation context: git clone (has source) vs global npm install (pre-built)
28
+ HAS_SOURCE=false
29
+ [[ -f "$GATE_KEEPER_DIR/tsconfig.json" ]] && HAS_SOURCE=true
30
+ HAS_DASHBOARD=false
31
+ [[ -d "$GATE_KEEPER_DIR/dashboard" ]] && HAS_DASHBOARD=true
32
+
33
+ echo "[gate-keeper] Detected: $([[ $HAS_SOURCE == true ]] && echo 'source build (git clone)' || echo 'pre-built package (npm global)')"
34
+
7
35
  # Kill old daemon process if it exists
8
36
  if [[ -f "$DAEMON_PID_FILE" ]]; then
9
37
  OLD_PID=$(cat "$DAEMON_PID_FILE")
@@ -26,54 +54,75 @@ done
26
54
 
27
55
  sleep 1
28
56
 
29
- echo "[gate-keeper] Installing dependencies..."
30
- cd "$GATE_KEEPER_DIR"
31
- npm install --silent
57
+ # ── Build from source (only for git clones) ──
58
+ if [[ $HAS_SOURCE == true ]]; then
59
+ echo "[gate-keeper] Installing dependencies..."
60
+ cd "$GATE_KEEPER_DIR"
61
+ npm install --silent
32
62
 
33
- echo "[gate-keeper] Building TypeScript..."
34
- npx tsc
63
+ echo "[gate-keeper] Building TypeScript..."
64
+ npx tsc
35
65
 
36
- echo "[gate-keeper] Building dashboard..."
37
- cd "$GATE_KEEPER_DIR/dashboard"
38
- npm install --silent
39
- npm run build
66
+ if [[ $HAS_DASHBOARD == true ]]; then
67
+ echo "[gate-keeper] Building dashboard..."
68
+ cd "$GATE_KEEPER_DIR/dashboard"
69
+ npm install --silent
70
+ npm run build
71
+ cd "$GATE_KEEPER_DIR"
72
+ fi
40
73
 
41
- echo ""
42
- echo "[gate-keeper] Build complete!"
43
- echo ""
74
+ echo ""
75
+ echo "[gate-keeper] Build complete!"
76
+ echo ""
77
+ else
78
+ echo "[gate-keeper] Pre-built package detected — skipping build steps"
79
+ fi
44
80
 
45
- # Start daemon in background (no auto-scan)
81
+ # ── Start daemon ──
46
82
  echo "[gate-keeper] Starting daemon..."
47
83
  cd "$GATE_KEEPER_DIR"
48
84
  nohup node dist/daemon.js --no-scan > /tmp/gk-daemon.log 2>&1 &
49
85
  NEW_PID=$!
50
86
  sleep 2
51
87
 
88
+ DAEMON_STARTED=false
52
89
  if kill -0 "$NEW_PID" 2>/dev/null; then
90
+ DAEMON_STARTED=true
53
91
  echo "[gate-keeper] Daemon started (PID: $NEW_PID)"
54
- echo "[gate-keeper] Dashboard: http://localhost:5378/viz"
55
92
  else
56
93
  echo "[gate-keeper] WARNING: Daemon failed to start. Check /tmp/gk-daemon.log"
57
94
  fi
58
95
 
59
- echo ""
60
- echo "[gate-keeper] Configuring Claude Code hooks..."
61
- CLAUDE_SETTINGS="$HOME/.claude/settings.json"
62
- if [[ ! -d "$HOME/.claude" ]]; then
63
- mkdir -p "$HOME/.claude"
64
- fi
96
+ # ── Write Claude Code hooks (only for claude or both) ──
97
+ if [[ "$MODE" == "claude" || "$MODE" == "both" ]]; then
98
+ echo ""
99
+ echo "[gate-keeper] Configuring Claude Code hooks..."
100
+ CLAUDE_SETTINGS="$HOME/.claude/settings.json"
101
+ if [[ ! -d "$HOME/.claude" ]]; then
102
+ mkdir -p "$HOME/.claude"
103
+ fi
65
104
 
66
- if [[ ! -f "$CLAUDE_SETTINGS" ]]; then
67
- cat > "$CLAUDE_SETTINGS" << EOF
105
+ if [[ ! -f "$CLAUDE_SETTINGS" ]]; then
106
+ cat > "$CLAUDE_SETTINGS" << SETUP_EOF
68
107
  {
69
108
  "hooks": {
109
+ "SessionStart": [
110
+ {
111
+ "hooks": [
112
+ {
113
+ "type": "command",
114
+ "command": "gate-keeper guide session-start"
115
+ }
116
+ ]
117
+ }
118
+ ],
70
119
  "PreToolUse": [
71
120
  {
72
121
  "matcher": "Write|Edit|MultiEdit",
73
122
  "hooks": [
74
123
  {
75
124
  "type": "command",
76
- "command": "node ${GATE_KEEPER_DIR}/dist/hook-pre-tool-use.js"
125
+ "command": "gate-keeper guide pre-edit"
77
126
  }
78
127
  ]
79
128
  }
@@ -84,30 +133,59 @@ if [[ ! -f "$CLAUDE_SETTINGS" ]]; then
84
133
  "hooks": [
85
134
  {
86
135
  "type": "command",
87
- "command": "node ${GATE_KEEPER_DIR}/dist/hook-receiver.js"
136
+ "command": "gate-keeper guide post-edit"
88
137
  }
89
138
  ]
90
139
  }
91
140
  ]
92
141
  }
93
142
  }
94
- EOF
95
- echo "[gate-keeper] ✓ Created ~/.claude/settings.json with Pre/PostToolUse hooks"
96
- else
97
- if ! grep -q "gate-keeper" "$CLAUDE_SETTINGS"; then
143
+ SETUP_EOF
144
+ echo "[gate-keeper] ✓ Created ~/.claude/settings.json with SessionStart/PreToolUse/PostToolUse hooks"
145
+ elif ! grep -q "gate-keeper" "$CLAUDE_SETTINGS"; then
98
146
  echo "[gate-keeper] ⚠ ~/.claude/settings.json exists but does not reference gate-keeper."
99
- echo "[gate-keeper] See README.md for the hook snippet to add manually."
147
+ echo "[gate-keeper] Manually add these hooks to enable guidance:"
148
+ echo "[gate-keeper] SessionStart: gate-keeper guide session-start"
149
+ echo "[gate-keeper] PreToolUse: gate-keeper guide pre-edit"
100
150
  else
101
151
  echo "[gate-keeper] ✓ Hooks already configured in ~/.claude/settings.json"
102
152
  fi
103
153
  fi
104
154
 
155
+ # ── Print Copilot / VS Code instructions (only for copilot or both) ──
156
+ if [[ "$MODE" == "copilot" || "$MODE" == "both" ]]; then
157
+ echo ""
158
+ echo "[gate-keeper] Copilot / VS Code setup:"
159
+ echo " Add these files to your repo:"
160
+ echo ""
161
+ echo " .vscode/tasks.json — auto-registers repo on folder open:"
162
+ echo ' { "version": "2.0.0", "tasks": [{ "label": "gate-keeper: register repo",'
163
+ echo ' "type": "shell", "command": "gate-keeper", "args": ["register"],'
164
+ echo ' "runOptions": { "runOn": "folderOpen" },'
165
+ echo ' "presentation": { "reveal": "never", "echo": false, "close": true },'
166
+ echo ' "problemMatcher": [] }] }'
167
+ echo ""
168
+ echo " .vscode/mcp.json — exposes 5 MCP tools to Copilot:"
169
+ echo ' { "servers": { "gate-keeper": { "command": "gate-keeper", "args": ["mcp"] } } }'
170
+ echo ""
171
+ echo " See README.md for the full instructions."
172
+ fi
173
+
174
+ # ── Summary ──
105
175
  echo ""
106
176
  echo "[gate-keeper] Setup Summary:"
107
177
  echo ""
108
- echo " ✓ Dependencies installed and built"
109
- echo " ✓ Daemon running on ports 5378 (WebSocket) and 5379 (IPC)"
110
- echo " ✓ Dashboard available at http://localhost:5378/viz"
178
+ if [[ $DAEMON_STARTED == true ]]; then
179
+ echo " ✓ Daemon running on ports 5378 (WebSocket) and 5379 (IPC)"
180
+ echo " ✓ Dashboard available at http://localhost:5378/viz"
181
+ else
182
+ echo " ✗ Daemon failed to start — check /tmp/gk-daemon.log"
183
+ fi
184
+ if [[ "$MODE" == "claude" || "$MODE" == "both" ]]; then
185
+ echo " ✓ Claude Code hooks configured in ~/.claude/settings.json"
186
+ fi
187
+ if [[ "$MODE" == "copilot" || "$MODE" == "both" ]]; then
188
+ echo " ✓ Copilot / VS Code: add .vscode/tasks.json and .vscode/mcp.json to your repo"
189
+ fi
111
190
  echo ""
112
- echo " Copilot / VS Code: instructions auto-load from .github/instructions/"
113
- echo " MCP server entry : node dist/mcp/server.js (or: npm run mcp)"
191
+ echo " MCP server : gate-keeper mcp"
@@ -1,416 +0,0 @@
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)