Files
ColaFlow/colaflow-api/src/ColaFlow.API/Program.cs
Yaojia Wang d11df78d1f fix(backend): Fix Dockerfile and add health check endpoint for Docker
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>
2025-11-04 23:25:22 +01:00

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 { }