using FluentValidation; using GreenHome.AI.DeepSeek; using GreenHome.Application; using GreenHome.Infrastructure; using GreenHome.Infrastructure.Data; using GreenHome.Sms.Ippanel; using GreenHome.VoiceCall.Avanak; using Microsoft.AspNetCore.StaticFiles; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.FileProviders; using Scalar.AspNetCore; var builder = WebApplication.CreateBuilder(args); // ============================================================ // 1. Service Registration // ============================================================ ConfigureCoreServices(builder); ConfigureCors(builder); ConfigureExternalServices(builder); var app = builder.Build(); // ============================================================ // 2. Pipeline Configuration // ============================================================ ApplyMigrations(app); ConfigureMiddleware(app); ConfigureStaticFiles(app); app.MapControllers(); // SPA fallback: serve index.html for non-API routes (client-side routing) app.MapFallbackToFile("index.html"); app.Run(); // ============================================================ // Service Registration Methods // ============================================================ void ConfigureCoreServices(WebApplicationBuilder builder) { builder.Services.AddControllers(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddOpenApi(); // Caching & HTTP client factory (used by WeatherController proxy with tiered caching) builder.Services.AddMemoryCache(); builder.Services.AddHttpClient(); builder.Services.AddAutoMapper(typeof(GreenHome.Application.MappingProfile)); builder.Services.AddValidatorsFromAssemblyContaining(); builder.Services.AddInfrastructure(builder.Configuration); } void ConfigureCors(WebApplicationBuilder builder) { const string CorsPolicy = "DefaultCors"; builder.Services.AddCors(options => { options.AddPolicy(CorsPolicy, policy => { if (builder.Environment.IsDevelopment()) { policy .AllowAnyOrigin() .AllowAnyHeader() .AllowAnyMethod(); } else { policy .WithOrigins( "http://green.nabaksoft.ir", "https://green.nabaksoft.ir", "http://gh1.nabaksoft.ir", "https://gh1.nabaksoft.ir" ) .AllowAnyHeader() .AllowAnyMethod() .AllowCredentials(); } }); }); } void ConfigureExternalServices(WebApplicationBuilder builder) { builder.Services.AddIppanelSms(builder.Configuration); builder.Services.AddAvanakVoiceCall(builder.Configuration); builder.Services.AddDeepSeek(builder.Configuration); } // ============================================================ // Middleware & Pipeline Methods // ============================================================ void ApplyMigrations(WebApplication app) { using var scope = app.Services.CreateScope(); var services = scope.ServiceProvider; try { var context = services.GetRequiredService(); context.Database.Migrate(); } catch (Exception ex) { var logger = services.GetRequiredService>(); logger.LogError(ex, "An error occurred while migrating the database."); } } void ConfigureMiddleware(WebApplication app) { app.MapOpenApi(); app.MapScalarApiReference(); if (!app.Environment.IsDevelopment()) { app.UseHttpsRedirection(); } app.UseCors("DefaultCors"); app.UseAuthorization(); } void ConfigureStaticFiles(WebApplication app) { var provider = new FileExtensionContentTypeProvider(); provider.Mappings[".amr"] = "audio/amr"; provider.Mappings[".apk"] = "application/vnd.android.package-archive"; // Serve existing My_StaticFiles folder app.UseStaticFiles(new StaticFileOptions { FileProvider = new PhysicalFileProvider( Path.Combine(app.Environment.ContentRootPath, "My_StaticFiles")), RequestPath = "/My_StaticFiles", ContentTypeProvider = provider }); // Serve new files folder for APK and other downloads app.UseStaticFiles(new StaticFileOptions { FileProvider = new PhysicalFileProvider( Path.Combine(app.Environment.ContentRootPath, "files")), RequestPath = "/files", ContentTypeProvider = provider }); // Serve the SPA from wwwroot (Vite build output) app.UseDefaultFiles(); app.UseStaticFiles(); }