using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication.Google; using Microsoft.AspNetCore.Authentication.MicrosoftAccount; using Microsoft.AspNetCore.Localization; using Microsoft.AspNetCore.StaticFiles; using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.Localization; using MongoDB.Driver; using QRRapidoApp.Data; using QRRapidoApp.Middleware; using QRRapidoApp.Services; using QRRapidoApp.Services.Monitoring; using QRRapidoApp.Services.HealthChecks; using StackExchange.Redis; using Stripe; using System.Globalization; using Serilog; using Serilog.Events; using Serilog.Sinks.SystemConsole.Themes; using Microsoft.AspNetCore.Mvc.Razor; var builder = WebApplication.CreateBuilder(args); // Configure Serilog Log.Logger = new LoggerConfiguration() .ReadFrom.Configuration(builder.Configuration) .Enrich.FromLogContext() .Enrich.WithEnvironmentName() .Enrich.WithProcessId() .Enrich.WithThreadId() .Enrich.WithProperty("ApplicationName", builder.Configuration["ApplicationName"] ?? "QRRapido") .Enrich.WithProperty("Environment", "Dev") .WriteTo.Async(a => a.Console( outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} {Properties:j}{NewLine}{Exception}", theme: AnsiConsoleTheme.Code)) .WriteTo.Async(a => a.Seq(builder.Configuration["Serilog:SeqUrl"], apiKey: builder.Configuration["Serilog:ApiKey"]=="" ? null : builder.Configuration["Serilog:ApiKey"])) .CreateLogger(); builder.Host.UseSerilog(); // Add services to the container builder.Services.AddControllersWithViews() .AddViewLocalization(Microsoft.AspNetCore.Mvc.Razor.LanguageViewLocationExpanderFormat.Suffix) .AddDataAnnotationsLocalization(); builder.Services.AddDistributedMemoryCache(); // Armazena os dados da sess�o na mem�ria builder.Services.AddSession(options => { options.Cookie.HttpOnly = true; options.Cookie.IsEssential = true; options.IdleTimeout = TimeSpan.FromMinutes(30); // Tempo de expira��o }); // Add HttpClient for health checks builder.Services.AddHttpClient(); // MongoDB Configuration - optional for development var mongoConnectionString = builder.Configuration.GetConnectionString("MongoDB"); if (!string.IsNullOrEmpty(mongoConnectionString)) { try { builder.Services.AddSingleton(serviceProvider => { return new MongoClient(mongoConnectionString); }); builder.Services.AddScoped(); } catch { // MongoDB not available - services will handle gracefully builder.Services.AddScoped(); } } else { // Development mode without MongoDB builder.Services.AddScoped(); } // Cache Configuration - use Redis if available, otherwise memory cache var redisConnectionString = builder.Configuration.GetConnectionString("Redis"); if (!string.IsNullOrEmpty(redisConnectionString)) { try { builder.Services.AddStackExchangeRedisCache(options => { options.Configuration = redisConnectionString; }); } catch { // Fallback to memory cache if Redis fails builder.Services.AddMemoryCache(); builder.Services.AddSingleton(); } } else { // Use memory cache when Redis is not configured builder.Services.AddMemoryCache(); builder.Services.AddSingleton(); } // Authentication Configuration builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie(options => { options.LoginPath = "/Account/Login"; options.LogoutPath = "/Account/Logout"; options.ExpireTimeSpan = TimeSpan.FromDays(30); options.SlidingExpiration = true; }) .AddGoogle(options => { options.ClientId = builder.Configuration["Authentication:Google:ClientId"]; options.ClientSecret = builder.Configuration["Authentication:Google:ClientSecret"]; // ADICIONE ESTAS LINHAS: options.Events.OnRedirectToAuthorizationEndpoint = context => { context.Response.Redirect(context.RedirectUri); return Task.CompletedTask; }; // OU use este método alternativo: options.Scope.Add("email"); options.Scope.Add("profile"); options.SaveTokens = true; }).AddMicrosoftAccount(MicrosoftAccountDefaults.AuthenticationScheme, options => { options.ClientId = builder.Configuration["Authentication:Microsoft:ClientId"]; options.ClientSecret = builder.Configuration["Authentication:Microsoft:ClientSecret"]; // ADICIONE ESTAS LINHAS: options.AuthorizationEndpoint = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize"; options.TokenEndpoint = "https://login.microsoftonline.com/common/oauth2/v2.0/token"; // Força sempre mostrar a tela de seleção de conta options.Events.OnRedirectToAuthorizationEndpoint = context => { context.Response.Redirect(context.RedirectUri + "&prompt=select_account"); return Task.CompletedTask; }; }); // Stripe Configuration StripeConfiguration.ApiKey = builder.Configuration["Stripe:SecretKey"]; // Localization builder.Services.AddLocalization(options => options.ResourcesPath = ""); //builder.Services.AddLocalization(options => options.ResourcesPath = "Resources"); builder.Services.Configure(options => { var supportedCultures = new[] { new CultureInfo("pt-BR"), new CultureInfo("es"), new CultureInfo("en") }; options.DefaultRequestCulture = new RequestCulture("pt-BR", "pt-BR"); options.SupportedCultures = supportedCultures; options.SupportedUICultures = supportedCultures; options.FallBackToParentCultures = true; options.FallBackToParentUICultures = true; // Clear default providers and add custom ones in priority order options.RequestCultureProviders.Clear(); options.RequestCultureProviders.Add(new RouteDataRequestCultureProvider()); options.RequestCultureProviders.Add(new QueryStringRequestCultureProvider()); options.RequestCultureProviders.Add(new CookieRequestCultureProvider()); }); // Custom Services builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); // Background Services builder.Services.AddHostedService(); // Monitoring Services if (builder.Configuration.GetValue("ResourceMonitoring:Enabled", true)) { builder.Services.AddHostedService(); } if (builder.Configuration.GetValue("MongoDbMonitoring:Enabled", true)) { builder.Services.AddHostedService(); } // CORS for API endpoints builder.Services.AddCors(options => { options.AddPolicy("AllowSpecificOrigins", policy => { policy.WithOrigins("https://qrrapido.site", "https://www.qrrapido.site") .AllowAnyHeader() .AllowAnyMethod(); }); }); // Health checks with custom implementations builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddHealthChecks() .AddCheck("mongodb") .AddCheck("seq") .AddCheck("resources") .AddCheck("external_services"); var app = builder.Build(); // Configure the HTTP request pipeline if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Home/Error"); app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); // Language redirection middleware (before routing) app.UseMiddleware(); app.UseRouting(); app.UseCors("AllowSpecificOrigins"); // Localization middleware app.UseRequestLocalization(); app.UseAuthentication(); app.UseAuthorization(); app.UseSession(); // Custom middleware app.UseMiddleware(); // Health check endpoint app.MapHealthChecks("/health"); //app.MapControllerRoute( // name: "auth", // pattern: "signin-{provider}", // defaults: new { controller = "Account", action = "ExternalLoginCallback" }); //app.MapControllerRoute( // name: "account", // pattern: "Account/{action}", // defaults: new { controller = "Account" }); // Language routes (must be first) app.MapControllerRoute( name: "localized", pattern: "{culture:regex(^(pt-BR|es|en)$)}/{controller=Home}/{action=Index}/{id?}"); // API routes app.MapControllerRoute( name: "api", pattern: "api/{controller}/{action=Index}/{id?}"); // Default fallback route (for development/testing without culture) app.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); try { Log.Information("Starting QRRapido application"); app.Run(); } catch (Exception ex) { Log.Fatal(ex, "QRRapido application terminated unexpectedly"); } finally { Log.CloseAndFlush(); }