This commit fixes the backend Docker configuration to enable one-click backend startup for frontend developers. Changes: - Updated Dockerfile with correct paths for modular monolith architecture * Added all module projects (Identity, ProjectManagement, IssueManagement) * Optimized layer caching by copying .csproj files first * Used alpine runtime image for smaller size (~500MB reduction) * Added non-root user (appuser) for security * Simplified to single HTTP port (8080) for development - Enhanced .dockerignore to optimize build context * Excluded unnecessary files (docs, git, docker files) * Added environment and secret file exclusions - Added /health endpoint to Program.cs * Required for Docker HEALTHCHECK functionality * Enables docker-compose to verify backend is ready Testing: - Docker build succeeds in ~14 seconds (after first build) - Backend container starts and passes health check - Swagger UI accessible at http://localhost:5000/scalar/v1 - Health endpoint returns "Healthy" at http://localhost:5000/health This implements Phase 1 of DOCKER-DEVELOPMENT-ENVIRONMENT.md 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
205 lines
6.6 KiB
C#
205 lines
6.6 KiB
C#
using ColaFlow.API.Extensions;
|
|
using ColaFlow.API.Handlers;
|
|
using ColaFlow.API.Hubs;
|
|
using ColaFlow.API.Middleware;
|
|
using ColaFlow.API.Services;
|
|
using ColaFlow.Modules.Identity.Application;
|
|
using ColaFlow.Modules.Identity.Infrastructure;
|
|
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
|
using Microsoft.IdentityModel.Tokens;
|
|
using Scalar.AspNetCore;
|
|
using System.Text;
|
|
|
|
var builder = WebApplication.CreateBuilder(args);
|
|
|
|
// Register ProjectManagement Module
|
|
builder.Services.AddProjectManagementModule(builder.Configuration, builder.Environment);
|
|
|
|
// Register IssueManagement Module
|
|
builder.Services.AddIssueManagementModule(builder.Configuration, builder.Environment);
|
|
|
|
// Register Identity Module
|
|
builder.Services.AddIdentityApplication();
|
|
builder.Services.AddIdentityInfrastructure(builder.Configuration, builder.Environment);
|
|
|
|
// Add Response Caching
|
|
builder.Services.AddResponseCaching();
|
|
builder.Services.AddMemoryCache();
|
|
|
|
// Add Response Compression (Gzip and Brotli)
|
|
builder.Services.AddResponseCompression(options =>
|
|
{
|
|
options.EnableForHttps = true;
|
|
options.Providers.Add<Microsoft.AspNetCore.ResponseCompression.BrotliCompressionProvider>();
|
|
options.Providers.Add<Microsoft.AspNetCore.ResponseCompression.GzipCompressionProvider>();
|
|
});
|
|
|
|
builder.Services.Configure<Microsoft.AspNetCore.ResponseCompression.BrotliCompressionProviderOptions>(options =>
|
|
{
|
|
options.Level = System.IO.Compression.CompressionLevel.Fastest;
|
|
});
|
|
|
|
builder.Services.Configure<Microsoft.AspNetCore.ResponseCompression.GzipCompressionProviderOptions>(options =>
|
|
{
|
|
options.Level = System.IO.Compression.CompressionLevel.Fastest;
|
|
});
|
|
|
|
// Add controllers with JSON string enum converter
|
|
builder.Services.AddControllers()
|
|
.AddJsonOptions(options =>
|
|
{
|
|
options.JsonSerializerOptions.Converters.Add(new System.Text.Json.Serialization.JsonStringEnumConverter());
|
|
});
|
|
|
|
// Configure exception handling (IExceptionHandler - .NET 8+)
|
|
builder.Services.AddExceptionHandler<GlobalExceptionHandler>();
|
|
builder.Services.AddProblemDetails();
|
|
|
|
// Configure Authentication
|
|
builder.Services.AddAuthentication(options =>
|
|
{
|
|
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
|
|
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
|
|
})
|
|
.AddJwtBearer(options =>
|
|
{
|
|
options.TokenValidationParameters = new TokenValidationParameters
|
|
{
|
|
ValidateIssuer = true,
|
|
ValidateAudience = true,
|
|
ValidateLifetime = true,
|
|
ValidateIssuerSigningKey = true,
|
|
ValidIssuer = builder.Configuration["Jwt:Issuer"],
|
|
ValidAudience = builder.Configuration["Jwt:Audience"],
|
|
IssuerSigningKey = new SymmetricSecurityKey(
|
|
Encoding.UTF8.GetBytes(builder.Configuration["Jwt:SecretKey"] ?? throw new InvalidOperationException("JWT SecretKey not configured")))
|
|
};
|
|
|
|
// Configure SignalR to use JWT from query string (for WebSocket upgrade)
|
|
options.Events = new JwtBearerEvents
|
|
{
|
|
OnMessageReceived = context =>
|
|
{
|
|
var accessToken = context.Request.Query["access_token"];
|
|
|
|
// If the request is for SignalR hub...
|
|
var path = context.HttpContext.Request.Path;
|
|
if (!string.IsNullOrEmpty(accessToken) && path.StartsWithSegments("/hubs"))
|
|
{
|
|
// Read the token from query string
|
|
context.Token = accessToken;
|
|
}
|
|
|
|
return Task.CompletedTask;
|
|
}
|
|
};
|
|
});
|
|
|
|
// Configure Authorization Policies for RBAC
|
|
builder.Services.AddAuthorization(options =>
|
|
{
|
|
// Tenant Owner only
|
|
options.AddPolicy("RequireTenantOwner", policy =>
|
|
policy.RequireRole("TenantOwner"));
|
|
|
|
// Tenant Owner or Tenant Admin
|
|
options.AddPolicy("RequireTenantAdmin", policy =>
|
|
policy.RequireRole("TenantOwner", "TenantAdmin"));
|
|
|
|
// Tenant Owner, Tenant Admin, or Tenant Member (excludes Guest and AIAgent)
|
|
options.AddPolicy("RequireTenantMember", policy =>
|
|
policy.RequireRole("TenantOwner", "TenantAdmin", "TenantMember"));
|
|
|
|
// Human users only (excludes AIAgent)
|
|
options.AddPolicy("RequireHumanUser", policy =>
|
|
policy.RequireAssertion(context =>
|
|
!context.User.IsInRole("AIAgent")));
|
|
|
|
// AI Agent only (for MCP integration testing)
|
|
options.AddPolicy("RequireAIAgent", policy =>
|
|
policy.RequireRole("AIAgent"));
|
|
});
|
|
|
|
// Configure CORS for frontend (SignalR requires AllowCredentials)
|
|
builder.Services.AddCors(options =>
|
|
{
|
|
options.AddPolicy("AllowFrontend", policy =>
|
|
{
|
|
policy.WithOrigins("http://localhost:3000", "https://localhost:3000")
|
|
.AllowAnyHeader()
|
|
.AllowAnyMethod()
|
|
.AllowCredentials(); // Required for SignalR
|
|
});
|
|
});
|
|
|
|
// Configure SignalR
|
|
builder.Services.AddSignalR(options =>
|
|
{
|
|
// Enable detailed errors (development only)
|
|
options.EnableDetailedErrors = builder.Environment.IsDevelopment();
|
|
|
|
// Client timeout settings
|
|
options.ClientTimeoutInterval = TimeSpan.FromSeconds(60);
|
|
options.HandshakeTimeout = TimeSpan.FromSeconds(15);
|
|
|
|
// Keep alive interval
|
|
options.KeepAliveInterval = TimeSpan.FromSeconds(15);
|
|
});
|
|
|
|
// Register Realtime Notification Service
|
|
builder.Services.AddScoped<IRealtimeNotificationService, RealtimeNotificationService>();
|
|
|
|
// Register Project Notification Service Adapter (for ProjectManagement module)
|
|
builder.Services.AddScoped<ColaFlow.Modules.ProjectManagement.Application.Services.IProjectNotificationService,
|
|
ProjectNotificationServiceAdapter>();
|
|
|
|
// Configure OpenAPI/Scalar
|
|
builder.Services.AddOpenApi();
|
|
|
|
// Add Health Checks (required for Docker health check)
|
|
builder.Services.AddHealthChecks();
|
|
|
|
var app = builder.Build();
|
|
|
|
// Configure the HTTP request pipeline
|
|
if (app.Environment.IsDevelopment())
|
|
{
|
|
app.MapOpenApi();
|
|
app.MapScalarApiReference();
|
|
}
|
|
|
|
// Performance logging (should be early to measure total request time)
|
|
app.UsePerformanceLogging();
|
|
|
|
// Global exception handler (should be first in pipeline)
|
|
app.UseExceptionHandler();
|
|
|
|
// Enable Response Compression (should be early in pipeline)
|
|
app.UseResponseCompression();
|
|
|
|
// Enable CORS
|
|
app.UseCors("AllowFrontend");
|
|
|
|
app.UseHttpsRedirection();
|
|
|
|
// Enable Response Caching (after HTTPS redirection)
|
|
app.UseResponseCaching();
|
|
|
|
// Authentication & Authorization
|
|
app.UseAuthentication();
|
|
app.UseAuthorization();
|
|
|
|
app.MapControllers();
|
|
|
|
// Map Health Check endpoint (required for Docker health check)
|
|
app.MapHealthChecks("/health");
|
|
|
|
// Map SignalR Hubs (after UseAuthorization)
|
|
app.MapHub<ProjectHub>("/hubs/project");
|
|
app.MapHub<NotificationHub>("/hubs/notification");
|
|
|
|
app.Run();
|
|
|
|
// Make the implicit Program class public for integration tests
|
|
public partial class Program { }
|