mirror of
https://github.com/Sea-Haven-Industries/proposal-system.git
synced 2026-09-30 17:03:14 +00:00
Add JsonStringEnumConverter so string enum values (ServiceCategory, Priority, PricingMode, LineItemSource) deserialize correctly from frontend requests. Wrap AuditService detail strings in a JSON object to satisfy the Postgres jsonb column type. Relax global.json SDK version to match installed 8.0.1xx feature band.
187 lines
6 KiB
C#
187 lines
6 KiB
C#
using System.Text;
|
|
using Amazon.S3;
|
|
using Amazon.SecretsManager;
|
|
using Amazon.SQS;
|
|
using FluentValidation;
|
|
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.IdentityModel.Tokens;
|
|
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<bool>("Auth:DevMode");
|
|
|
|
// AWS SDK clients (skip in dev mode — no real AWS credentials needed)
|
|
if (!devMode)
|
|
{
|
|
builder.Services.AddDefaultAWSOptions(builder.Configuration.GetAWSOptions());
|
|
builder.Services.AddAWSService<IAmazonS3>();
|
|
builder.Services.AddAWSService<IAmazonSQS>();
|
|
builder.Services.AddAWSService<IAmazonSecretsManager>();
|
|
}
|
|
|
|
// 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<ProposalDbContext>(options =>
|
|
options.UseNpgsql(connectionString));
|
|
}
|
|
else
|
|
{
|
|
builder.Services.AddDbContext<ProposalDbContext>(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;
|
|
options.TokenValidationParameters = new TokenValidationParameters
|
|
{
|
|
ValidateIssuerSigningKey = true,
|
|
ValidateIssuer = true,
|
|
ValidateAudience = false,
|
|
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
|
|
{
|
|
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
|
|
.AddJwtBearer();
|
|
}
|
|
|
|
builder.Services.AddAuthorization();
|
|
|
|
// Services
|
|
builder.Services.AddHttpContextAccessor();
|
|
builder.Services.AddScoped<ICurrentUserService, CurrentUserService>();
|
|
builder.Services.AddScoped<IProposalService, ProposalService>();
|
|
builder.Services.AddScoped<ILineItemService, LineItemService>();
|
|
builder.Services.AddScoped<ICustomerService, CustomerService>();
|
|
builder.Services.AddScoped<IAuditService, AuditService>();
|
|
builder.Services.AddScoped<IProposalNumberGenerator, ProposalNumberGenerator>();
|
|
if (devMode)
|
|
builder.Services.AddScoped<IS3Service, DevS3Service>();
|
|
else
|
|
builder.Services.AddScoped<IS3Service, S3Service>();
|
|
builder.Services.AddScoped<ISimilarProposalService, SimilarProposalService>();
|
|
var jobsQueueUrl = builder.Configuration["JOBS_QUEUE_URL"] ?? "";
|
|
if (string.IsNullOrEmpty(jobsQueueUrl))
|
|
{
|
|
builder.Services.AddScoped<IJobPublisher, NoOpJobPublisher>();
|
|
}
|
|
else
|
|
{
|
|
builder.Services.AddScoped<IJobPublisher>(sp =>
|
|
{
|
|
var sqsClient = sp.GetRequiredService<IAmazonSQS>();
|
|
return new SqsJobPublisher(sqsClient, jobsQueueUrl);
|
|
});
|
|
}
|
|
|
|
// HTTP client for Cognito token exchange
|
|
builder.Services.AddHttpClient();
|
|
|
|
// Validation
|
|
builder.Services.AddValidatorsFromAssemblyContaining<CreateProposalValidator>();
|
|
|
|
// Controllers
|
|
builder.Services.AddControllers(options =>
|
|
{
|
|
options.Filters.Add<ValidationFilter>();
|
|
}).AddJsonOptions(options =>
|
|
{
|
|
options.JsonSerializerOptions.Converters.Add(new System.Text.Json.Serialization.JsonStringEnumConverter());
|
|
});
|
|
|
|
// Middleware
|
|
builder.Services.AddTransient<GlobalExceptionHandler>();
|
|
|
|
// Health checks
|
|
builder.Services.AddHealthChecks()
|
|
.AddDbContextCheck<ProposalDbContext>();
|
|
|
|
// CORS
|
|
builder.Services.AddCors(options =>
|
|
{
|
|
options.AddDefaultPolicy(policy =>
|
|
{
|
|
policy.WithOrigins(
|
|
"https://proposals.seahaven.com",
|
|
"http://localhost:5173")
|
|
.AllowAnyMethod()
|
|
.AllowAnyHeader();
|
|
});
|
|
});
|
|
|
|
// Lambda hosting
|
|
builder.Services.AddAWSLambdaHosting(LambdaEventSource.HttpApi);
|
|
|
|
var app = builder.Build();
|
|
|
|
app.UseMiddleware<GlobalExceptionHandler>();
|
|
app.UseCors();
|
|
app.UseMiddleware<InternalApiKeyMiddleware>();
|
|
app.UseAuthentication();
|
|
app.UseAuthorization();
|
|
app.Use(async (context, next) =>
|
|
{
|
|
if (context.User.Identity?.IsAuthenticated == true)
|
|
{
|
|
var userService = context.RequestServices.GetRequiredService<ICurrentUserService>();
|
|
await userService.ResolveAsync();
|
|
}
|
|
await next();
|
|
});
|
|
app.MapControllers();
|
|
app.MapHealthChecks("/api/health");
|
|
|
|
app.Run();
|