Files
Backend-Api/ServiceHost/Program.cs
2026-02-10 15:20:36 +03:30

393 lines
15 KiB
C#

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<CreateUserCommandValidators>();
builder.Services.AddScoped<IDataSeeder, DataSeeder>();
builder.Services.AddScoped<IBoardNotificationPublisher, SignalRBoardNotificationPublisher>();
#region MongoDb
var mongoConnectionSection = builder.Configuration.GetSection("MongoDb");
var mongoDbSettings = mongoConnectionSection.Get<MongoDbConfig>();
var mongoClient = new MongoClient(mongoDbSettings.ConnectionString);
var mongoDatabase = mongoClient.GetDatabase(mongoDbSettings.DatabaseName);
builder.Services.AddSingleton<IMongoDatabase>(mongoDatabase);
#endregion
builder.Services.AddSingleton<IActionResultExecutor<JsonResult>, 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<IPasswordHasher, PasswordHasher>();
builder.Services.AddTransient<IFileUploader, FileUploader>();
builder.Services.AddTransient<IAuthHelper, AuthHelper>();
builder.Services.AddTransient<IGoogleRecaptcha, GoogleRecaptcha>();
builder.Services.AddTransient<ISmsService, SmsService>();
builder.Services.AddTransient<IUidService, UidService>();
builder.Services.AddTransient<IFaceEmbeddingNotificationService, FaceEmbeddingNotificationService>();
#region Mahan
builder.Services.AddTransient<Tester>();
builder.Services.Configure<AppSettingConfiguration>(builder.Configuration);
#endregion
builder.Services.Configure<FormOptions>(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<CookiePolicyOptions>(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<SecurityPageFilter>());
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<string[]>() ?? Array.Empty<string>();
options.AddPolicy("AllowSpecificOrigins", policy =>
{
policy.WithOrigins(corsOrigins)
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials();
});
});
#endregion
builder.Services.AddExceptionHandler<CustomExceptionHandler>();
var sepehrTerminalId = builder.Configuration.GetValue<long>("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<ForwardedHeadersOptions>(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<Tester>();
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<RazorJsonEnumOverrideMiddleware>();
#endregion
app.MapHub<CreateContractTarckingHub>("/trackingHub");
app.MapHub<SendAccountMessage>("/trackingSmsHub");
app.MapHub<HolidayApiHub>("/trackingHolidayHub");
app.MapHub<CheckoutHub>("/trackingCheckoutHub");
app.MapHub<SendSmsHub>("/trackingSendSmsHub");
app.MapHub<ProjectBoardHub>("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();
}