using System.Text; using Amazon.DynamoDBv2; using Amazon.S3; using Amazon.SecretsManager; using Amazon.SQS; using FluentValidation; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.EntityFrameworkCore; using Microsoft.IdentityModel.Tokens; using Microsoft.OpenApi.Models; using ProposalSystem.Api.Middleware; using ProposalSystem.Api.Services; using ProposalSystem.Application.Interfaces; using ProposalSystem.Application.Validators; using ProposalSystem.Infrastructure.Data; using ProposalSystem.Infrastructure.Services; var builder = WebApplication.CreateBuilder(args); // Dev mode flag (read early for conditional setup) var devMode = builder.Configuration.GetValue("Auth:DevMode") && builder.Environment.IsDevelopment(); // AWS SDK clients (skip in dev mode — no real AWS credentials needed) if (!devMode) { builder.Services.AddDefaultAWSOptions(builder.Configuration.GetAWSOptions()); builder.Services.AddAWSService(); builder.Services.AddAWSService(); builder.Services.AddAWSService(); builder.Services.AddAWSService(); } // Database var dbSecretArn = builder.Configuration["DB_SECRET_ARN"]; if (!string.IsNullOrEmpty(dbSecretArn)) { var smClient = new AmazonSecretsManagerClient(); var connectionString = SecretsManagerConnectionString.ResolveAsync(smClient, dbSecretArn).GetAwaiter().GetResult(); builder.Services.AddDbContext(options => options.UseNpgsql(connectionString)); } else { builder.Services.AddDbContext(options => options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection"))); } // Internal API key (for Lambda-to-API calls) var internalApiKeySecretArn = builder.Configuration["INTERNAL_API_KEY_SECRET_ARN"]; if (!string.IsNullOrEmpty(internalApiKeySecretArn)) { var smClient = new AmazonSecretsManagerClient(); var secretResponse = smClient.GetSecretValueAsync(new Amazon.SecretsManager.Model.GetSecretValueRequest { SecretId = internalApiKeySecretArn, }).GetAwaiter().GetResult(); builder.Configuration["INTERNAL_API_KEY"] = secretResponse.SecretString; } // Authentication var cognitoAuthority = builder.Configuration["Auth:Authority"]; if (!string.IsNullOrEmpty(cognitoAuthority)) { builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.Authority = cognitoAuthority; var webClientId = builder.Configuration["COGNITO_WEB_CLIENT_ID"] ?? ""; var mobileClientId = builder.Configuration["COGNITO_MOBILE_CLIENT_ID"] ?? ""; options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuerSigningKey = true, ValidateIssuer = true, ValidateAudience = true, ValidAudiences = new[] { webClientId, mobileClientId }.Where(s => !string.IsNullOrEmpty(s)).ToList(), ValidateLifetime = true, RoleClaimType = "cognito:groups", }; }); } else if (devMode) { var devSigningKey = builder.Configuration["Auth:DevSigningKey"]!; builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuerSigningKey = true, IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(devSigningKey)), ValidateIssuer = true, ValidIssuer = "proposal-system-dev", ValidateAudience = true, ValidAudience = "proposal-system-dev", ValidateLifetime = true, RoleClaimType = "cognito:groups", }; }); } else { throw new InvalidOperationException( "Authentication is not configured. Set Auth:Authority for Cognito or Auth:DevMode=true (Development only)."); } builder.Services.AddAuthorization(); // Services builder.Services.AddHttpContextAccessor(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); if (devMode) { builder.Services.AddScoped(); builder.Services.AddScoped(); } else { builder.Services.AddScoped(); builder.Services.AddScoped(); } builder.Services.AddScoped(); var jobsQueueUrl = builder.Configuration["JOBS_QUEUE_URL"] ?? ""; if (string.IsNullOrEmpty(jobsQueueUrl)) { builder.Services.AddScoped(); } else { builder.Services.AddScoped(sp => { var sqsClient = sp.GetRequiredService(); return new SqsJobPublisher(sqsClient, jobsQueueUrl); }); } // HTTP client for Cognito token exchange builder.Services.AddHttpClient(); // Validation builder.Services.AddValidatorsFromAssemblyContaining(); // Controllers builder.Services.AddControllers(options => { options.Filters.Add(); }).AddJsonOptions(options => { options.JsonSerializerOptions.Converters.Add(new System.Text.Json.Serialization.JsonStringEnumConverter()); }); // OpenAPI / Swagger builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(options => { options.SwaggerDoc("v1", new OpenApiInfo { Title = "Proposal System API", Version = "v1", Description = "Sea Haven Industries proposal management API", }); options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme { Name = "Authorization", Type = SecuritySchemeType.Http, Scheme = "bearer", BearerFormat = "JWT", In = ParameterLocation.Header, Description = "Cognito JWT access token", }); options.AddSecurityRequirement(new OpenApiSecurityRequirement { { new OpenApiSecurityScheme { Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "Bearer" }, }, Array.Empty() }, }); }); // Middleware builder.Services.AddTransient(); // Health checks builder.Services.AddHealthChecks() .AddDbContextCheck(); // CORS builder.Services.AddCors(options => { options.AddDefaultPolicy(policy => { var origins = new List { "https://proposals.seahaven.com" }; if (builder.Environment.IsDevelopment()) origins.Add("http://localhost:5173"); policy.WithOrigins(origins.ToArray()) .AllowAnyMethod() .AllowAnyHeader(); }); }); // Lambda hosting builder.Services.AddAWSLambdaHosting(LambdaEventSource.HttpApi); var app = builder.Build(); app.UseMiddleware(); if (!app.Environment.IsProduction()) { app.UseSwagger(); app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "Proposal System API v1")); } app.UseCors(); app.UseMiddleware(); app.UseAuthentication(); app.UseAuthorization(); app.Use(async (context, next) => { if (context.User.Identity?.IsAuthenticated == true) { var userService = context.RequestServices.GetRequiredService(); await userService.ResolveAsync(); } await next(); }); app.MapControllers(); app.MapHealthChecks("/api/health"); app.Run();