This is the initial commit for the CODEX_ADK project, a full-stack AI agent management platform featuring: BACKEND (ASP.NET Core 8.0): - CQRS architecture with 6 commands and 7 queries - 16 API endpoints (all working and tested) - PostgreSQL database with 5 entities - AES-256 encryption for API keys - FluentValidation on all commands - Rate limiting and CORS configured - OpenAPI/Swagger documentation - Docker Compose setup (PostgreSQL + Ollama) FRONTEND (Flutter 3.x): - Dark theme with Svrnty branding - Collapsible sidebar navigation - CQRS API client with Result<T> error handling - Type-safe endpoints from OpenAPI schema - Multi-platform support (Web, iOS, Android, macOS, Linux, Windows) DOCUMENTATION: - Comprehensive API reference - Architecture documentation - Development guidelines for Claude Code - API integration guides - context-claude.md project overview Status: Backend ready (Grade A-), Frontend integration pending 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
225 lines
6.8 KiB
C#
225 lines
6.8 KiB
C#
using System.Text.Json.Serialization;
|
|
using System.Threading.RateLimiting;
|
|
using Codex.Api;
|
|
using Codex.Api.Endpoints;
|
|
using Codex.Api.Middleware;
|
|
using Codex.Dal;
|
|
using FluentValidation.AspNetCore;
|
|
using Microsoft.AspNetCore.HttpOverrides;
|
|
using Microsoft.AspNetCore.RateLimiting;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using OpenHarbor.CQRS;
|
|
using OpenHarbor.CQRS.DynamicQuery.Abstractions;
|
|
using PoweredSoft.Data;
|
|
using PoweredSoft.Data.EntityFrameworkCore;
|
|
using PoweredSoft.DynamicQuery;
|
|
using PoweredSoft.Module.Abstractions;
|
|
using OpenHarbor.CQRS.AspNetCore.Mvc;
|
|
using OpenHarbor.CQRS.DynamicQuery.AspNetCore;
|
|
|
|
var builder = WebApplication.CreateBuilder(args);
|
|
|
|
builder.Services.Configure<ForwardedHeadersOptions>(options =>
|
|
{
|
|
options.ForwardedHeaders =
|
|
ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedHost;
|
|
|
|
options.KnownNetworks.Clear();
|
|
options.KnownProxies.Clear();
|
|
options.ForwardLimit = 2;
|
|
});
|
|
|
|
builder.Services.AddHttpContextAccessor();
|
|
|
|
// Configure CORS for Flutter and web clients
|
|
builder.Services.AddCors(options =>
|
|
{
|
|
options.AddDefaultPolicy(policy =>
|
|
{
|
|
if (builder.Environment.IsDevelopment())
|
|
{
|
|
// Development: Allow any localhost port + Capacitor/Ionic
|
|
policy.SetIsOriginAllowed(origin =>
|
|
{
|
|
var uri = new Uri(origin);
|
|
return uri.Host == "localhost" ||
|
|
origin.StartsWith("capacitor://", StringComparison.OrdinalIgnoreCase) ||
|
|
origin.StartsWith("ionic://", StringComparison.OrdinalIgnoreCase);
|
|
})
|
|
.AllowAnyMethod()
|
|
.AllowAnyHeader()
|
|
.AllowCredentials();
|
|
}
|
|
else
|
|
{
|
|
// Production: Use configured origins only
|
|
var allowedOrigins = builder.Configuration
|
|
.GetSection("Cors:AllowedOrigins")
|
|
.Get<string[]>() ?? Array.Empty<string>();
|
|
|
|
if (allowedOrigins.Length > 0)
|
|
{
|
|
policy.WithOrigins(allowedOrigins)
|
|
.AllowAnyMethod()
|
|
.AllowAnyHeader()
|
|
.AllowCredentials();
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
// Add rate limiting (MVP: generous limits to prevent runaway loops)
|
|
builder.Services.AddRateLimiter(options =>
|
|
{
|
|
options.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(context =>
|
|
{
|
|
var clientId = context.User?.Identity?.Name ?? context.Connection.RemoteIpAddress?.ToString() ?? "anonymous";
|
|
|
|
return RateLimitPartition.GetFixedWindowLimiter(
|
|
partitionKey: clientId,
|
|
factory: _ => new FixedWindowRateLimiterOptions
|
|
{
|
|
PermitLimit = 1000,
|
|
Window = TimeSpan.FromMinutes(1),
|
|
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
|
|
QueueLimit = 0
|
|
});
|
|
});
|
|
|
|
options.OnRejected = async (context, cancellationToken) =>
|
|
{
|
|
context.HttpContext.Response.StatusCode = StatusCodes.Status429TooManyRequests;
|
|
await context.HttpContext.Response.WriteAsJsonAsync(new
|
|
{
|
|
error = "Rate limit exceeded",
|
|
message = "Too many requests. Please wait before trying again.",
|
|
retryAfter = "60 seconds"
|
|
}, cancellationToken: cancellationToken);
|
|
};
|
|
});
|
|
|
|
builder.Services.AddPoweredSoftDataServices();
|
|
builder.Services.AddPoweredSoftEntityFrameworkCoreDataServices();
|
|
builder.Services.AddPoweredSoftDynamicQuery();
|
|
builder.Services.AddDefaultCommandDiscovery();
|
|
builder.Services.AddDefaultQueryDiscovery();
|
|
|
|
builder.Logging.ClearProviders();
|
|
builder.Logging.AddConsole();
|
|
builder.Services.AddHttpClient();
|
|
builder.Services.AddMemoryCache();
|
|
|
|
builder.Services
|
|
.AddFluentValidationAutoValidation()
|
|
.AddFluentValidationClientsideAdapters();
|
|
|
|
builder.Services.AddModule<AppModule>();
|
|
|
|
var mvcBuilder = builder.Services
|
|
.AddControllers()
|
|
.AddJsonOptions(jsonOptions =>
|
|
{
|
|
jsonOptions.JsonSerializerOptions.Converters.Insert(0, new JsonStringEnumConverter());
|
|
});
|
|
|
|
mvcBuilder
|
|
.AddOpenHarborCommands();
|
|
|
|
mvcBuilder
|
|
.AddOpenHarborQueries()
|
|
.AddOpenHarborDynamicQueries();
|
|
|
|
// Register PostgreSQL DbContext
|
|
builder.Services.AddDbContext<CodexDbContext>(options =>
|
|
options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection")));
|
|
|
|
if (builder.Environment.IsDevelopment())
|
|
{
|
|
builder.Services.AddEndpointsApiExplorer();
|
|
builder.Services.AddSwaggerGen(options =>
|
|
{
|
|
options.SwaggerDoc("v1", new Microsoft.OpenApi.Models.OpenApiInfo
|
|
{
|
|
Title = "Codex API",
|
|
Version = "v1",
|
|
Description = "CQRS-based API using OpenHarbor.CQRS framework"
|
|
});
|
|
|
|
// Include XML comments from all projects
|
|
var xmlFiles = new[]
|
|
{
|
|
"Codex.Api.xml",
|
|
"Codex.CQRS.xml",
|
|
"Codex.Dal.xml"
|
|
};
|
|
|
|
foreach (var xmlFile in xmlFiles)
|
|
{
|
|
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
|
|
if (File.Exists(xmlPath))
|
|
{
|
|
options.IncludeXmlComments(xmlPath);
|
|
}
|
|
}
|
|
|
|
// Add authentication scheme documentation (for future use)
|
|
options.AddSecurityDefinition("Bearer", new Microsoft.OpenApi.Models.OpenApiSecurityScheme
|
|
{
|
|
Description = "JWT Authorization header using the Bearer scheme. Example: \"Bearer {token}\"",
|
|
Name = "Authorization",
|
|
In = Microsoft.OpenApi.Models.ParameterLocation.Header,
|
|
Type = Microsoft.OpenApi.Models.SecuritySchemeType.ApiKey,
|
|
Scheme = "Bearer"
|
|
});
|
|
|
|
options.AddSecurityRequirement(new Microsoft.OpenApi.Models.OpenApiSecurityRequirement
|
|
{
|
|
{
|
|
new Microsoft.OpenApi.Models.OpenApiSecurityScheme
|
|
{
|
|
Reference = new Microsoft.OpenApi.Models.OpenApiReference
|
|
{
|
|
Type = Microsoft.OpenApi.Models.ReferenceType.SecurityScheme,
|
|
Id = "Bearer"
|
|
}
|
|
},
|
|
Array.Empty<string>()
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
var app = builder.Build();
|
|
|
|
// Global exception handler (must be first)
|
|
app.UseMiddleware<GlobalExceptionHandler>();
|
|
|
|
// Rate limiting (before CORS and routing)
|
|
app.UseRateLimiter();
|
|
|
|
// Use CORS policy configured from appsettings
|
|
app.UseCors();
|
|
|
|
if (app.Environment.IsDevelopment())
|
|
{
|
|
app.UseSwagger();
|
|
app.UseSwaggerUI();
|
|
}
|
|
|
|
app.UseForwardedHeaders();
|
|
|
|
if (builder.Environment.IsDevelopment() == false)
|
|
{
|
|
app.UseHttpsRedirection();
|
|
}
|
|
|
|
// Map OpenHarbor auto-generated endpoints (CreateAgent, UpdateAgent, DeleteAgent, GetAgent, Health)
|
|
app.MapControllers();
|
|
|
|
// Map manually registered endpoints (commands with return values, queries with return types)
|
|
app.MapCodexEndpoints();
|
|
|
|
// Map simple GET endpoints for lists (pragmatic MVP approach)
|
|
app.MapSimpleEndpoints();
|
|
|
|
app.Run(); |