2026-07-13 13:22:53 -03:00
using Api.SeaHavenIndustries.Helper ;
2026-07-09 14:08:48 -03:00
using Api.SeaHavenIndustries.HostedServices ;
2026-07-13 13:22:53 -03:00
using Api.SeaHavenIndustries.Middleware ;
2026-09-04 14:11:32 -03:00
using Api.SeaHavenIndustries.Observability ;
2026-07-09 14:08:48 -03:00
using Api.SeaHavenIndustries.Options ;
2024-09-28 14:58:36 -04:00
using Data.SeaHavenIndustries ;
using Microsoft.AspNetCore.Authentication.JwtBearer ;
using Microsoft.AspNetCore.Identity ;
using Microsoft.AspNetCore.ResponseCompression ;
using Microsoft.EntityFrameworkCore ;
using Microsoft.IdentityModel.Tokens ;
using Microsoft.OpenApi.Models ;
2026-09-04 14:11:32 -03:00
using Sentry.AspNetCore ;
using Sentry.Extensibility ;
2024-09-28 14:58:36 -04:00
using System.Text ;
2026-05-14 11:00:12 -05:00
using SeaHaven.DataServices.DependencyInjection ;
using SeaHaven.Services.DependencyInjection ;
2026-07-13 13:22:12 -03:00
using SeaHaven.Services.Implementation ;
2026-07-24 17:35:34 -03:00
using SeaHaven.Services.Interfaces ;
2024-09-28 14:58:36 -04:00
var builder = WebApplication . CreateBuilder ( args ) ;
2026-09-04 14:11:32 -03:00
var entryAssembly = System . Reflection . Assembly . GetEntryAssembly ( ) ;
var release = SentryObservability . ResolveRelease ( entryAssembly ) ;
var commitSha = SentryObservability . ResolveCommitSha ( entryAssembly ) ;
builder . WebHost . UseSentry ( ( SentryAspNetCoreOptions options ) = >
{
options . Dsn = builder . Configuration [ "SENTRY_DSN" ] ? ? string . Empty ;
options . Environment = builder . Configuration [ "SENTRY_ENVIRONMENT" ]
? ? builder . Environment . EnvironmentName . ToLowerInvariant ( ) ;
options . Release = release ;
options . DefaultTags . Add ( "service" , SentryObservability . ServiceName ) ;
options . DefaultTags . Add ( "app.commit" , commitSha ? ? SentryObservability . LocalDevelopmentRelease ) ;
options . TracesSampleRate = 1.0 ;
options . SendDefaultPii = false ;
options . MaxRequestBodySize = RequestSize . None ;
options . SetBeforeSend ( SentryTelemetryScrubber . Scrub ) ;
options . SetBeforeSendTransaction ( SentryTelemetryScrubber . Scrub ) ;
} ) ;
2024-09-28 14:58:36 -04:00
ConfigurationManager configuration = builder . Configuration ;
builder . Services . AddDbContext < ApplicationDbContext > ( options = > options . UseSqlServer ( configuration . GetConnectionString ( "DefaultConnection" ) ) ) ;
builder . Services . AddIdentity < ApplicationUser , IdentityRole > ( options = >
{
options . User . RequireUniqueEmail = false ;
} )
. AddEntityFrameworkStores < ApplicationDbContext > ( )
. AddDefaultTokenProviders ( ) ;
2026-06-30 13:20:41 -03:00
builder . Services . AddControllers ( options = >
{
options . Filters . Add < Api . SeaHavenIndustries . Filters . ConcurrencyExceptionFilter > ( ) ;
2026-07-17 14:31:08 -03:00
options . Filters . Add < Api . SeaHavenIndustries . Filters . ArgumentExceptionFilter > ( ) ;
2026-06-30 13:20:41 -03:00
} ) . AddJsonOptions ( options = >
2024-09-28 14:58:36 -04:00
{
options . JsonSerializerOptions . MaxDepth = 264 ; // or any other desired value
} ) ;
builder . Services . AddResponseCompression ( opts = >
{
opts . MimeTypes = ResponseCompressionDefaults . MimeTypes . Concat (
new [ ] { "application/octet-stream" } ) ;
} ) ;
builder . Services . AddCors ( option = >
option . AddDefaultPolicy ( builder = > builder . AllowAnyOrigin ( ) . AllowAnyHeader ( ) . AllowAnyMethod ( ) ) ) ;
2026-05-14 11:00:12 -05:00
2024-09-28 14:58:36 -04:00
builder . Services . AddScoped < SendMessage > ( ) ;
2026-07-24 17:35:34 -03:00
builder . Services . AddScoped < Api . SeaHavenIndustries . Helper . SendGridEmailSender > ( ) ;
builder . Services . AddScoped < SeaHaven . Services . Interfaces . IEmailSender , Api . SeaHavenIndustries . Helper . SendGridEmailSender > ( ) ;
2026-07-23 19:18:06 -03:00
builder . Services . AddSingleton ( TimeProvider . System ) ;
builder . Services . AddScoped < IDocumentScanner , ClamAvDocumentScanner > ( ) ;
builder . Services . AddHostedService < VendorDocumentScanWorker > ( ) ;
2026-04-17 11:56:06 -04:00
builder . Services . AddSingleton < Api . SeaHavenIndustries . Helper . ZipCodeDistance > ( sp = >
{
var zipService = new Api . SeaHavenIndustries . Helper . ZipCodeDistance ( ) ;
var env = sp . GetRequiredService < IWebHostEnvironment > ( ) ;
var csvPath = Path . Combine ( env . WebRootPath , "zipcodes.csv" ) ;
if ( File . Exists ( csvPath ) ) zipService . Load ( csvPath ) ;
return zipService ;
} ) ;
2026-07-24 17:35:34 -03:00
builder . Services . AddSingleton < SeaHaven . Services . Interfaces . IZipCodeDistance > (
serviceProvider = > serviceProvider . GetRequiredService < Api . SeaHavenIndustries . Helper . ZipCodeDistance > ( ) ) ;
2026-04-28 18:55:14 -05:00
2026-05-14 11:00:12 -05:00
builder . Services . AddDataServices ( ) ;
2026-07-24 17:35:34 -03:00
builder . Services . AddBusinessServices ( configuration ) ;
builder . Services . AddScoped < SeaHaven . Services . Interfaces . ISyncExternalSource , Api . SeaHavenIndustries . Infrastructure . DynamoSyncExternalSource > ( ) ;
builder . Services . AddScoped < SeaHaven . Services . Interfaces . IFileStoragePort , Api . SeaHavenIndustries . Infrastructure . FileStorageAdapter > ( ) ;
builder . Services . AddScoped < SeaHaven . Services . Interfaces . IDispatchEmailPort , Api . SeaHavenIndustries . Infrastructure . DispatchEmailAdapter > ( ) ;
builder . Services . AddScoped < SeaHaven . Services . Interfaces . IVendorTokenPort , Api . SeaHavenIndustries . Infrastructure . VendorTokenAdapter > ( ) ;
builder . Services . AddScoped < SeaHaven . Services . Interfaces . IVendorDocumentStoragePort , Api . SeaHavenIndustries . Infrastructure . VendorDocumentStorageAdapter > ( ) ;
2026-07-24 21:03:50 -03:00
builder . Services . AddSingleton < Amazon . SecretsManager . IAmazonSecretsManager > ( _ = >
{
var region = builder . Configuration [ $"{SeaHaven.Services.Configuration.WorkOrderWebhookOptions.SectionName}:Region" ] ;
return string . IsNullOrWhiteSpace ( region )
? new Amazon . SecretsManager . AmazonSecretsManagerClient ( )
: new Amazon . SecretsManager . AmazonSecretsManagerClient ( Amazon . RegionEndpoint . GetBySystemName ( region ) ) ;
} ) ;
builder . Services . AddSingleton <
SeaHaven . Services . Interfaces . IWorkOrderWebhookSecretProvider ,
Api . SeaHavenIndustries . Infrastructure . AwsWorkOrderWebhookSecretProvider > ( ) ;
2026-07-24 22:13:25 -03:00
builder . Services . AddSingleton <
Api . SeaHavenIndustries . Infrastructure . IProcurementAwsCredentialsProvider ,
Api . SeaHavenIndustries . Infrastructure . DefaultProcurementAwsCredentialsProvider > ( ) ;
builder . Services . AddHttpClient <
SeaHaven . Services . Interfaces . IProcurementWorkOrderClient ,
Api . SeaHavenIndustries . Infrastructure . ProcurementWorkOrderClient > ( )
. ConfigurePrimaryHttpMessageHandler ( ( ) = > new HttpClientHandler
{
AllowAutoRedirect = false
} ) ;
2026-04-28 18:55:14 -05:00
2026-07-09 14:08:48 -03:00
builder . Services . Configure < WorkOrderJobsOptions > (
builder . Configuration . GetSection ( WorkOrderJobsOptions . SectionName ) ) ;
2026-07-24 22:13:25 -03:00
builder . Services . AddHostedService < WorkOrderReconciliationHostedService > ( ) ;
2026-07-13 13:22:12 -03:00
builder . Services . AddOptions < WorkOrderIngestOptions > ( )
. Bind ( builder . Configuration . GetSection ( WorkOrderIngestOptions . SectionName ) )
. Validate (
o = > ! o . Enabled
| | ( ! string . IsNullOrWhiteSpace ( o . ApiKey )
& & ! o . ApiKey . Contains ( "${" , StringComparison . Ordinal )
& & o . ApiKey . Length > = 32 ) ,
"WorkOrderIngest:ApiKey must be a real secret (env/user-secrets, length >= 32) when ingest is enabled." )
. ValidateOnStart ( ) ;
builder . Services . Configure < SyncOptions > (
builder . Configuration . GetSection ( SyncOptions . SectionName ) ) ;
builder . Services . Configure < LegacyEndpointOptions > (
builder . Configuration . GetSection ( LegacyEndpointOptions . SectionName ) ) ;
builder . Services . AddSingleton < WorkOrderJobRunStateAccessor > ( ) ;
2026-07-24 17:35:34 -03:00
builder . Services . AddSingleton < IWorkOrderJobRunStateAccessor > (
sp = > sp . GetRequiredService < WorkOrderJobRunStateAccessor > ( ) ) ;
2026-07-13 13:22:12 -03:00
builder . Services . AddSingleton ( sp = > sp . GetRequiredService < WorkOrderJobRunStateAccessor > ( ) . State ) ;
builder . Services . AddScoped < Api . SeaHavenIndustries . Filters . IngestApiKeyFilter > ( ) ;
2026-07-09 14:08:48 -03:00
builder . Services . AddHostedService < WorkOrderWeekRolledHostedService > ( ) ;
builder . Services . AddHostedService < PastDueCacheHostedService > ( ) ;
2026-08-11 08:58:19 -03:00
builder . Services . AddHostedService < UpliftLifecycleHostedService > ( ) ;
2026-07-13 13:22:12 -03:00
builder . Services . AddOptions < SeaHaven . Services . Implementation . WorkOrderOpsHealthOptions > ( )
. Configure < Microsoft . Extensions . Options . IOptions < SyncOptions > ,
Microsoft . Extensions . Options . IOptions < WorkOrderIngestOptions > ,
Microsoft . Extensions . Options . IOptions < LegacyEndpointOptions > > (
( health , sync , ingest , legacy ) = >
{
health . SyncEnabled = sync . Value . Enabled ;
health . IngestEnabled = ingest . Value . Enabled ;
health . LegacyDeprecationEnabled = legacy . Value . DeprecationEnabled ;
health . LegacySunsetDate = legacy . Value . SunsetDate ;
} ) ;
2026-07-09 14:08:48 -03:00
2024-09-28 14:58:36 -04:00
builder . Services . AddAuthentication ( options = >
{
options . DefaultAuthenticateScheme = JwtBearerDefaults . AuthenticationScheme ;
options . DefaultChallengeScheme = JwtBearerDefaults . AuthenticationScheme ;
options . DefaultScheme = JwtBearerDefaults . AuthenticationScheme ;
} )
. AddJwtBearer ( options = >
{
options . SaveToken = true ;
options . RequireHttpsMetadata = false ;
options . TokenValidationParameters = new TokenValidationParameters ( )
{
ValidateIssuer = true ,
ValidateAudience = true ,
ValidAudience = configuration [ "JWT:ValidAudience" ] ,
ValidIssuer = configuration [ "JWT:ValidIssuer" ] ,
2026-07-24 17:35:34 -03:00
IssuerSigningKey = new SymmetricSecurityKey ( Encoding . UTF8 . GetBytes (
configuration [ "JWT:Secret" ]
? ? throw new InvalidOperationException ( "JWT:Secret configuration is required" ) ) )
2024-09-28 14:58:36 -04:00
} ;
} ) ;
builder . Services . AddEndpointsApiExplorer ( ) ;
builder . Services . AddSwaggerGen ( c = >
{
c . SwaggerDoc ( "v1" , new OpenApiInfo { Title = "Seahaven" , Version = "v1" } ) ;
c . AddSecurityDefinition ( "Bearer" , new OpenApiSecurityScheme ( )
{
Name = "Authorization" ,
Type = SecuritySchemeType . ApiKey ,
Scheme = "Bearer" ,
BearerFormat = "JWT" ,
In = ParameterLocation . Header ,
Description = "JWT Authorization header using the Bearer scheme. \r\n\r\n Enter 'Bearer' [space] and then your token in the text input below.\r\n\r\nExample: \"Bearer 12345abcdef\"" ,
} ) ;
c . AddSecurityRequirement ( new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType . SecurityScheme ,
Id = "Bearer"
}
} ,
new string [ ] { }
}
} ) ;
} ) ;
var app = builder . Build ( ) ;
// Configure the HTTP request pipeline.
if ( app . Environment . IsDevelopment ( ) | | app . Environment . IsProduction ( ) )
{
app . UseSwagger ( ) ;
app . UseSwaggerUI ( c = >
{
c . SwaggerEndpoint ( "/swagger/v1/swagger.json" , "Sari" ) ;
c . RoutePrefix = string . Empty ;
} ) ;
}
2026-04-16 16:25:03 -04:00
if ( ! app . Environment . IsDevelopment ( ) )
app . UseHttpsRedirection ( ) ;
2024-09-28 14:58:36 -04:00
app . UseStaticFiles ( ) ;
app . UseCors ( ) ;
2026-07-13 13:22:53 -03:00
app . UseMiddleware < LegacyDeprecationMiddleware > ( ) ;
2026-09-04 14:11:32 -03:00
app . UseRouting ( ) ;
2024-09-28 14:58:36 -04:00
app . UseAuthentication ( ) ;
2026-09-04 14:11:32 -03:00
app . UseMiddleware < SentryRequestMetadataMiddleware > ( ) ;
2024-09-28 14:58:36 -04:00
app . UseAuthorization ( ) ;
app . MapControllers ( ) ;
2026-09-16 18:26:20 -03:00
using ( var ownerScope = app . Services . CreateScope ( ) )
{
await ownerScope . ServiceProvider
. GetRequiredService < IAccountOwnerDesignationService > ( )
. EnsureConfiguredOwnerAsync ( CancellationToken . None ) ;
}
2026-04-24 12:12:16 -05:00
//if (app.Environment.IsDevelopment())
//{
// using var scope = app.Services.CreateScope();
// var userManager = scope.ServiceProvider.GetRequiredService<UserManager<ApplicationUser>>();
// var roleManager = scope.ServiceProvider.GetRequiredService<RoleManager<IdentityRole>>();
// if (!await roleManager.RoleExistsAsync("Admin"))
// await roleManager.CreateAsync(new IdentityRole("Admin"));
// if (!await roleManager.RoleExistsAsync("Dispatcher"))
// await roleManager.CreateAsync(new IdentityRole("Dispatcher"));
// if (!await roleManager.RoleExistsAsync("Manager"))
// await roleManager.CreateAsync(new IdentityRole("Manager"));
// var adminEmail = "admin@seahavenind.com";
// if (await userManager.FindByNameAsync(adminEmail) == null)
// {
// var admin = new ApplicationUser
// {
// UserName = adminEmail,
// Email = adminEmail,
// FirstName = "Admin",
// LastName = "User",
// EmailConfirmed = true,
// UniqueName = "Active",
// CreatedDate = DateTime.Now
// };
// var result = await userManager.CreateAsync(admin, "Admin@123");
// if (result.Succeeded)
// await userManager.AddToRoleAsync(admin, "Admin");
// }
// var db = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
// if (!db.DropdownOptions.Any())
// {
// var trades = new[] { "Plumbing", "Electrical", "HVAC", "Fire/Life Safety", "General", "Roofing", "Janitorial", "Painting", "Locksmith", "Carpentry" };
// for (int i = 0; i < trades.Length; i++)
// db.DropdownOptions.Add(new DropdownOption { Category = "Trade", Value = trades[i], SortOrder = i });
// var subTrades = new (string trade, string sub)[] {
// ("Plumbing", "Drain Cleaning"), ("Plumbing", "Pipe Repair"), ("Plumbing", "Fixture Install"), ("Plumbing", "Backflow Testing"),
// ("Electrical", "Lighting"), ("Electrical", "Panel/Breaker"), ("Electrical", "Wiring"), ("Electrical", "Generator"),
// ("HVAC", "AC Repair"), ("HVAC", "Heating"), ("HVAC", "Ductwork"), ("HVAC", "Controls/BMS"),
// ("Fire/Life Safety", "Sprinkler"), ("Fire/Life Safety", "Fire Alarm"), ("Fire/Life Safety", "Extinguisher"), ("Fire/Life Safety", "Fire Pump"),
// ("General", "Doors"), ("General", "Flooring"), ("General", "Drywall"), ("General", "Concrete"),
// };
// for (int i = 0; i < subTrades.Length; i++)
// db.DropdownOptions.Add(new DropdownOption { Category = "SubTrade", Value = subTrades[i].sub, ParentValue = subTrades[i].trade, SortOrder = i });
// var problems = new[] { "Leak", "No Power", "Not Cooling", "Not Heating", "Clogged", "Broken/Damaged", "Alarm Fault", "Preventive Maintenance", "Inspection", "Installation", "Other" };
// for (int i = 0; i < problems.Length; i++)
// db.DropdownOptions.Add(new DropdownOption { Category = "Problem", Value = problems[i], SortOrder = i });
// await db.SaveChangesAsync();
// }
//}
2026-04-16 16:25:03 -04:00
2024-09-28 14:58:36 -04:00
app . Run ( ) ;