using System.Text; using System.Text.Json.Serialization; using FluentValidation; using Hangfire; using Hangfire.MemoryStorage; using Hangfire.PostgreSql; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.IdentityModel.Tokens; using Meezi.API.Hubs; using Meezi.API.Configuration; using Meezi.API.Jobs; using Meezi.API.Services; using Meezi.API.Services.Delivery; using Meezi.Infrastructure.Services.Platform; using Meezi.API.Services.Printing; using Meezi.Core.Interfaces; using Meezi.Infrastructure; using Serilog; using StackExchange.Redis; namespace Meezi.API.Extensions; public static class ServiceCollectionExtensions { public static IServiceCollection AddMeeziServices(this IServiceCollection services, IConfiguration configuration) { services.AddHttpContextAccessor(); services.AddMeeziSecurity(configuration); services.AddInfrastructure(configuration); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddSingleton(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddSingleton(); services.Configure(configuration.GetSection(MenuAi3dOptions.SectionName)); services.AddHttpClient("MenuAi3d"); services.AddHttpClient("OpenAi"); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddSingleton(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.Configure(configuration.GetSection(DeliveryPlatformsOptions.SectionName)); services.PostConfigure(opts => { if (string.IsNullOrEmpty(opts.Snappfood.WebhookSecret)) opts.Snappfood.WebhookSecret = configuration["Snappfood:WebhookSecret"] ?? ""; if (string.IsNullOrEmpty(opts.Snappfood.ApiKey)) opts.Snappfood.ApiKey = configuration["Snappfood:ApiKey"] ?? ""; if (string.IsNullOrEmpty(opts.Snappfood.ApiBaseUrl)) opts.Snappfood.ApiBaseUrl = configuration["Snappfood:ApiBaseUrl"] ?? ""; }); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddHttpClient(nameof(PosDeviceService)); services.AddScoped(); services.AddScoped(); services.AddControllers() .AddJsonOptions(options => { options.JsonSerializerOptions.PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.CamelCase; options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); }); services.AddEndpointsApiExplorer(); services.AddSwaggerGen(); services.AddSignalR(); services.AddValidatorsFromAssemblyContaining(); var jwtKey = configuration["Jwt:Key"] ?? "meezi-dev-secret-key-min-32-chars!!"; var jwtIssuer = configuration["Jwt:Issuer"] ?? "meezi"; var jwtAudience = configuration["Jwt:Audience"] ?? "meezi"; services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidIssuer = jwtIssuer, ValidAudience = jwtAudience, IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtKey)) }; options.Events = new JwtBearerEvents { OnMessageReceived = context => { var accessToken = context.Request.Query["access_token"]; var path = context.HttpContext.Request.Path; if (!string.IsNullOrEmpty(accessToken) && path.StartsWithSegments("/hubs")) context.Token = accessToken; return Task.CompletedTask; } }; }); services.AddAuthorization(); var isTesting = configuration.GetValue("Testing:Enabled"); var redisConnection = configuration.GetConnectionString("Redis") ?? "localhost:6379"; if (isTesting) { services.AddSingleton(_ => ConnectionMultiplexer.Connect($"{redisConnection},abortConnect=false,connectTimeout=500")); } else { services.AddSingleton(_ => ConnectionMultiplexer.Connect(redisConnection)); } var connectionString = configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found."); services.AddHangfire(config => { config .SetDataCompatibilityLevel(CompatibilityLevel.Version_180) .UseSimpleAssemblyNameTypeSerializer() .UseRecommendedSerializerSettings(); if (isTesting) config.UseMemoryStorage(); else config.UsePostgreSqlStorage(options => options.UseNpgsqlConnection(connectionString)); }); if (!isTesting) services.AddHangfireServer(); services.AddCors(options => { options.AddPolicy("MeeziCors", policy => { var origins = configuration.GetSection("Cors:Origins").Get() ?? ["http://localhost:3000"]; policy.WithOrigins(origins) .AllowAnyHeader() .AllowAnyMethod() .AllowCredentials(); }); }); return services; } public static WebApplication ConfigureMeeziPipeline(this WebApplication app) { app.UseSerilogRequestLogging(); if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection(); var uploadsPath = Path.Combine(app.Environment.ContentRootPath, "uploads"); Directory.CreateDirectory(uploadsPath); app.UseStaticFiles(new StaticFileOptions { FileProvider = new Microsoft.Extensions.FileProviders.PhysicalFileProvider(uploadsPath), RequestPath = "/uploads" }); app.UseCors("MeeziCors"); app.UseRouting(); app.UseMeeziSecurity(); app.UseAuthentication(); app.UseMiddleware(); app.UseMiddleware(); app.UseAuthorization(); app.MapControllers(); app.MapHub("/hubs/kds"); app.MapHub("/hubs/guest-order"); app.MapGet("/health", () => Results.Ok(new { status = "healthy" })); if (!app.Configuration.GetValue("Testing:Enabled")) { app.UseHangfireDashboard("/hangfire"); RecurringJob.AddOrUpdate( "subscription-renewal-reminder", job => job.ExecuteAsync(), Cron.Daily(6)); RecurringJob.AddOrUpdate( "daily-reports-yesterday", job => job.ExecuteAsync(), "5 0 * * *", new RecurringJobOptions { TimeZone = IranCalendar.TimeZone }); RecurringJob.AddOrUpdate( "branch-permanent-delete", job => job.ExecuteAsync(), Cron.Hourly); } return app; } }