using System.Net; using System.Reflection; using _0_Framework.Application.Sms; using _0_Framework.Application; using AccountManagement.Configuration; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Http.Features; using PersonalContractingParty.Config; using ServiceHost; using Query.Bootstrapper; using ServiceHost.Hubs; using ServiceHost.MiddleWare; using WorkFlow.Infrastructure.Config; using _0_Framework.Application.UID; using _0_Framework.Exceptions.Handler; using _0_Framework.Application.FaceEmbedding; using ServiceHost.Test; using System.Text.Json.Serialization; using _0_Framework.InfraStructure.Mongo; using CompanyManagment.App.Contracts.Hubs; using CompanyManagment.EFCore.Services; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc; using MongoDB.Driver; using Parbad.Builder; using Parbad.Gateway.Sepehr; using Swashbuckle.AspNetCore.SwaggerUI; using AccountManagement.Domain.InternalApiCaller; using FluentValidation; using GozareshgirProgramManager.Application._Bootstrapper; using GozareshgirProgramManager.Application.Interfaces; using GozareshgirProgramManager.Application.Modules.Users.Commands.CreateUser; using GozareshgirProgramManager.Infrastructure; using GozareshgirProgramManager.Infrastructure.Persistence.Seed; using Microsoft.AspNetCore.HttpOverrides; using Serilog; using Serilog.Events; using ServiceHost.Hubs.ProgramManager; using ServiceHost.Notifications.ProgramManager; using ServiceHost.Conventions; using ServiceHost.Filters; using Microsoft.Extensions.FileProviders; using Microsoft.OpenApi; // Corrected using for PhysicalFileProvider // ==================================================================== // ✅ BEST PRACTICE: Use two-stage Serilog initialization to log startup errors. // ==================================================================== // Use Docker-compatible log path var logDirectory = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") == Environments.Development ? @"C:\LogsGozareshgir\\" : "/app/Logs"; if (!Directory.Exists(logDirectory)) { Directory.CreateDirectory(logDirectory); } // Bootstrap logger: Catches errors during host configuration Log.Logger = new LoggerConfiguration() .MinimumLevel.Information() .WriteTo.Console() .CreateBootstrapLogger(); Log.Information("Starting web host..."); try { var builder = WebApplication.CreateBuilder(args); // ==================================================================== // ✅ STANDARD SERILOG CONFIGURATION FOR PRODUCTION // ==================================================================== builder.Host.UseSerilog((context, services, configuration) => configuration .ReadFrom.Configuration(context.Configuration) // Optional: Allows config from appsettings.json .ReadFrom.Services(services) .Enrich.FromLogContext() .MinimumLevel.Information() // Default minimum level for your application's own logs .MinimumLevel.Override("Microsoft", LogEventLevel.Warning) // Suppress noisy Microsoft logs .MinimumLevel.Override("Microsoft.Hosting.Lifetime", LogEventLevel.Information) // ✅ KEEP THIS: Shows "Now listening on..." .MinimumLevel.Override("Microsoft.EntityFrameworkCore.Database.Command", LogEventLevel.Warning) // Suppresses EF query logs .WriteTo.Console(outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}") .WriteTo.File( path: Path.Combine(logDirectory, "gozareshgir_log.txt"), rollingInterval: RollingInterval.Day, retainedFileCountLimit: 30, shared: true, outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] [{SourceContext}] {Message:lj}{NewLine}{Exception}" )); builder.WebHost.ConfigureKestrel(serverOptions => { serverOptions.Limits.MaxRequestBodySize = long.MaxValue; }); builder.Services.AddRazorPages() .AddRazorRuntimeCompilation(); #region Register Services builder.Services.AddHttpContextAccessor(); builder.Services.AddHttpClient("holidayApi", c => c.BaseAddress = new Uri("https://api.github.com")); var connectionString = builder.Configuration.GetConnectionString("MesbahDb"); var connectionStringTestDb = builder.Configuration.GetConnectionString("TestDb"); builder.Services.AddProgramManagerApplication(); builder.Services.AddProgramManagerInfrastructure(builder.Configuration); builder.Services.AddValidatorsFromAssemblyContaining(); builder.Services.AddScoped(); builder.Services.AddScoped(); #region MongoDb var mongoConnectionSection = builder.Configuration.GetSection("MongoDb"); var mongoDbSettings = mongoConnectionSection.Get(); var mongoClient = new MongoClient(mongoDbSettings.ConnectionString); var mongoDatabase = mongoClient.GetDatabase(mongoDbSettings.DatabaseName); builder.Services.AddSingleton(mongoDatabase); #endregion builder.Services.AddSingleton, CustomJsonResultExecutor>(); PersonalBootstrapper.Configure(builder.Services, connectionString); TestDbBootStrapper.Configure(builder.Services, connectionStringTestDb); AccountManagementBootstrapper.Configure(builder.Services, connectionString); WorkFlowBootstrapper.Configure(builder.Services, connectionString); QueryBootstrapper.Configure(builder.Services); builder.Services.AddSingleton(); builder.Services.AddTransient(); builder.Services.AddTransient(); builder.Services.AddTransient(); builder.Services.AddTransient(); builder.Services.AddTransient(); builder.Services.AddTransient(); #region Mahan builder.Services.AddTransient(); builder.Services.Configure(builder.Configuration); #endregion builder.Services.Configure(options => { options.ValueCountLimit = int.MaxValue; options.KeyLengthLimit = int.MaxValue; options.ValueLengthLimit = int.MaxValue; options.MultipartBodyLengthLimit = long.MaxValue; options.MemoryBufferThreshold = int.MaxValue; options.MultipartHeadersLengthLimit = int.MaxValue; }); builder.Services.Configure(options => { options.CheckConsentNeeded = context => true; }); var domain = builder.Configuration["Domain"]; builder.Services.ConfigureApplicationCookie(options => { options.Cookie.HttpOnly = true; options.Cookie.SameSite = SameSiteMode.None; options.Cookie.SecurePolicy = CookieSecurePolicy.Always; options.Cookie.Domain = domain; }); builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, o => { o.LoginPath = new PathString("/"); o.LogoutPath = new PathString("/index"); o.AccessDeniedPath = new PathString("/AccessDenied"); o.ExpireTimeSpan = TimeSpan.FromHours(10); o.SlidingExpiration = true; }); builder.Services.AddAuthorization(options => { options.AddPolicy("AdminArea", builder => builder.RequireClaim("AccountId").RequireClaim("AdminAreaPermission", "true")); options.AddPolicy("ClientArea", builder => builder.RequireClaim("AccountId").RequireClaim("ClientAriaPermission", "true")); options.AddPolicy("CameraArea", builder => builder.RequireClaim("AccountId")); options.AddPolicy("AdminNewArea", builder => builder.RequireClaim("AccountId").RequireClaim("AdminAreaPermission", "true")); }); builder.Services.AddControllers(options => { options.Conventions.Add(new ParameterBindingConvention()); options.Filters.Add(new OperationResultFilter()); }) .AddJsonOptions(options => { options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); }); builder.Services.AddRazorPages(options => options.Conventions.AuthorizeAreaFolder("Admin", "/", "AdminArea")); builder.Services.AddRazorPages(options => options.Conventions.AuthorizeAreaFolder("Client", "/", "ClientArea")) .AddMvcOptions(options => options.Filters.Add()); builder.Services.AddRazorPages(options => options.Conventions.AuthorizeAreaFolder("Camera", "/", "CameraArea")); builder.Services.AddRazorPages(options => options.Conventions.AuthorizeAreaFolder("AdminNew", "/", "AdminNewArea")); builder.Services.AddMvc(); builder.Services.AddSignalR(); #endregion #region Swagger builder.Services.AddSwaggerGen(options => { options.UseInlineDefinitionsForEnums(); options.CustomSchemaIds(type => type.FullName); var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile); options.IncludeXmlComments(xmlPath); var classLibraryXmlFile = "CompanyManagment.App.Contracts.xml"; var classLibraryXmlPath = Path.Combine(AppContext.BaseDirectory, classLibraryXmlFile); options.IncludeXmlComments(classLibraryXmlPath); options.SwaggerDoc("General", new OpenApiInfo { Title = "API - General", Version = "v1" }); options.SwaggerDoc("Admin", new OpenApiInfo { Title = "API - Admin", Version = "v1" }); options.SwaggerDoc("Client", new OpenApiInfo { Title = "API - Client", Version = "v1" }); options.SwaggerDoc("Camera", new OpenApiInfo { Title = "API - Camera", Version = "v1" }); options.SwaggerDoc("ProgramManager", new OpenApiInfo { Title = "API - ProgramManager", Version = "v1" }); options.DocInclusionPredicate((docName, apiDesc) => string.Equals(docName, apiDesc.GroupName, StringComparison.OrdinalIgnoreCase)); options.EnableAnnotations(); }); #endregion #region CORS builder.Services.AddCors(options => { var corsOrigins = builder.Configuration.GetSection("CorsOrigins").Get() ?? Array.Empty(); options.AddPolicy("AllowSpecificOrigins", policy => { policy.WithOrigins(corsOrigins) .AllowAnyHeader() .AllowAnyMethod() .AllowCredentials(); }); }); #endregion builder.Services.AddExceptionHandler(); var sepehrTerminalId = builder.Configuration.GetValue("SepehrGateWayTerminalId"); builder.Services.AddParbad().ConfigureGateways(gateways => { gateways.AddSepehr().WithAccounts(accounts => { accounts.AddInMemory(account => { account.TerminalId = sepehrTerminalId; account.Name = "Sepehr Account"; }); }); }).ConfigureHttpContext(httpContext => httpContext.UseDefaultAspNetCore()) .ConfigureStorage(storage => { storage.UseMemoryCache(); }); builder.Services.Configure(options => { options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto; var proxies = builder.Configuration["KNOWN_PROXIES"]; if (!string.IsNullOrWhiteSpace(proxies)) { foreach (var proxy in proxies.Split(',', StringSplitOptions.RemoveEmptyEntries)) { options.KnownProxies.Add(IPAddress.Parse(proxy.Trim())); } } }); var app = builder.Build(); // ==================================================================== // ✅ HTTP PIPELINE CONFIGURATION // ==================================================================== app.UseCors("AllowSpecificOrigins"); #region InternalProgarmManagerApi app.Use(async (context, next) => { var host = context.Request.Host.Host?.ToLower() ?? ""; string baseUrl = host switch { var h when h.Contains("localhost") => builder.Configuration["InternalApi:Local"], var h when h.Contains("dadmehrg.ir") => builder.Configuration["InternalApi:Dadmehrg"], var h when h.Contains("gozareshgir.ir") => builder.Configuration["InternalApi:Gozareshgir"], _ => builder.Configuration["InternalApi:Local"] }; InternalApiCaller.SetBaseUrl(baseUrl); await next.Invoke(); }); #endregion #region Mahan if (builder.Environment.IsDevelopment()) { using var scope = app.Services.CreateScope(); var tester = scope.ServiceProvider.GetRequiredService(); await tester.Test(); } if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(options => { options.DocExpansion(DocExpansion.None); options.SwaggerEndpoint("/swagger/General/swagger.json", "API - General"); options.SwaggerEndpoint("/swagger/Admin/swagger.json", "API - Admin"); options.SwaggerEndpoint("/swagger/Client/swagger.json", "API - Client"); options.SwaggerEndpoint("/swagger/Camera/swagger.json", "API - Camera"); options.SwaggerEndpoint("/swagger/ProgramManager/swagger.json", "API - ProgramManager"); }); } #endregion app.UseForwardedHeaders(); if (builder.Environment.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseHsts(); } app.UseExceptionHandler(options => { }); app.Use(async (context, next) => { if (context.Request.Path.HasValue) { context.Request.Path = context.Request.Path.Value.ToLowerInvariant(); } await next(); }); app.UseStaticFiles(); var uploadsPath = builder.Configuration["FileStorage:LocalPath"] ?? Path.Combine(Directory.GetCurrentDirectory(), "Storage"); if (!Directory.Exists(uploadsPath)) { Directory.CreateDirectory(uploadsPath); } app.UseStaticFiles(new StaticFileOptions { FileProvider = new PhysicalFileProvider(uploadsPath), RequestPath = "/storage", OnPrepareResponse = ctx => { ctx.Context.Response.Headers.Append("Cache-Control", "public,max-age=2592000"); } }); app.UseRouting(); app.UseWebSockets(); app.UseCookiePolicy(); app.UseAuthentication(); app.UseAuthorization(); #region Mahan app.UseMiddleware(); #endregion app.MapHub("/trackingHub"); app.MapHub("/trackingSmsHub"); app.MapHub("/trackingHolidayHub"); app.MapHub("/trackingCheckoutHub"); app.MapHub("/trackingSendSmsHub"); app.MapHub("api/pm/board"); app.MapRazorPages(); app.MapControllers(); app.MapGet("/health", () => Results.Ok(new { status = "Healthy", timestamp = DateTime.UtcNow })); app.Run(); } catch (Exception ex) { Log.Fatal(ex, "Host terminated unexpectedly"); } finally { Log.CloseAndFlush(); }