Files
meezi/src/Meezi.API/Extensions/ServiceCollectionExtensions.cs
T
soroush.asadi 345ae0a4b5
CI/CD / CI · Admin API (dotnet build) (push) Successful in 41s
CI/CD / CI · Admin Web (tsc) (push) Failing after 5s
CI/CD / CI · Website (tsc) (push) Failing after 4s
CI/CD / CI · Koja (tsc) (push) Failing after 5s
CI/CD / CI · API (dotnet build + test) (push) Successful in 1m13s
CI/CD / CI · Dashboard (tsc) (push) Failing after 2m32s
CI/CD / Deploy · all services (push) Has been skipped
first commit
2026-05-31 11:06:24 +03:30

249 lines
11 KiB
C#

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<IAuthService, AuthService>();
services.AddScoped<IAuditLogService, AuditLogService>();
services.AddScoped<IConsumerAuthService, ConsumerAuthService>();
services.AddScoped<IConsumerOrdersService, ConsumerOrdersService>();
services.AddScoped<IKitchenStationService, KitchenStationService>();
services.AddScoped<INotificationInboxService, NotificationInboxService>();
services.AddScoped<IOrderNotificationService, OrderNotificationService>();
services.AddScoped<IWebsiteService, Meezi.Infrastructure.Services.WebsiteService>();
services.AddScoped<IJwtTokenService, JwtTokenService>();
services.AddSingleton<IRefreshTokenStore, RedisRefreshTokenStore>();
services.AddScoped<IPlanLimitChecker, PlanLimitChecker>();
services.AddScoped<IMenuService, MenuService>();
services.AddScoped<IBranchMenuService, BranchMenuService>();
services.AddScoped<IBranchIdentityService, BranchIdentityService>();
services.AddScoped<ITableService, TableService>();
services.AddScoped<IOrderService, OrderService>();
services.AddScoped<IKdsNotifier, KdsNotifier>();
services.AddScoped<ICustomerService, CustomerService>();
services.AddScoped<ICouponService, CouponService>();
services.AddScoped<ITaxService, TaxService>();
services.AddSingleton<IMediaStorageService, MediaStorageService>();
services.Configure<MenuAi3dOptions>(configuration.GetSection(MenuAi3dOptions.SectionName));
services.AddHttpClient("MenuAi3d");
services.AddHttpClient("OpenAi");
services.AddScoped<IOpenAiChatService, OpenAiChatService>();
services.AddScoped<ICoffeeAdvisorService, CoffeeAdvisorService>();
services.AddScoped<IMenuAi3dGenerationService, MenuAi3dGenerationService>();
services.AddScoped<IBranchLifecycleService, BranchLifecycleService>();
services.AddSingleton<ITerminalRegistryService, TerminalRegistryService>();
services.AddScoped<IInventoryService, InventoryService>();
services.AddScoped<ILoyaltyService, LoyaltyService>();
services.AddScoped<ISmsMarketingService, SmsMarketingService>();
services.AddScoped<IHrService, HrService>();
services.AddScoped<IReportService, ReportService>();
services.AddScoped<IDailyReportService, DailyReportService>();
services.AddScoped<IPublicService, PublicService>();
services.AddScoped<IReviewService, ReviewService>();
services.AddScoped<IPushDeviceService, PushDeviceService>();
services.AddScoped<IBillingService, BillingService>();
services.AddScoped<IBillingPaymentOrchestrator, BillingPaymentOrchestrator>();
services.Configure<DeliveryPlatformsOptions>(configuration.GetSection(DeliveryPlatformsOptions.SectionName));
services.PostConfigure<DeliveryPlatformsOptions>(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<IWebhookSignatureService, WebhookSignatureService>();
services.AddScoped<IOrderNormalizer, OrderNormalizer>();
services.AddScoped<ICommissionCalculator, CommissionCalculator>();
services.AddScoped<IDeliveryOrderProcessor, DeliveryOrderProcessor>();
services.AddScoped<IDeliveryWebhookIngressService, DeliveryWebhookIngressService>();
services.AddScoped<IDeliveryStatusSyncService, DeliveryStatusSyncService>();
services.AddScoped<IDeliveryFinanceReportService, DeliveryFinanceReportService>();
services.AddScoped<ProcessDeliveryOrderJob>();
services.AddScoped<ISnappfoodWebhookService, SnappfoodWebhookService>();
services.AddScoped<IReservationService, ReservationService>();
services.AddScoped<IQueueService, QueueService>();
services.AddScoped<IShiftService, ShiftService>();
services.AddScoped<IExpenseService, ExpenseService>();
services.AddScoped<ReceiptBuilder>();
services.AddScoped<IPrinterService, NetworkPrinterService>();
services.AddHttpClient(nameof(PosDeviceService));
services.AddScoped<IPosDeviceService, PosDeviceService>();
services.AddScoped<SubscriptionRenewalReminderJob>();
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<Program>();
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<bool>("Testing:Enabled");
var redisConnection = configuration.GetConnectionString("Redis") ?? "localhost:6379";
if (isTesting)
{
services.AddSingleton<IConnectionMultiplexer>(_ =>
ConnectionMultiplexer.Connect($"{redisConnection},abortConnect=false,connectTimeout=500"));
}
else
{
services.AddSingleton<IConnectionMultiplexer>(_ =>
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<string[]>()
?? ["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<Middleware.TenantMiddleware>();
app.UseMiddleware<Middleware.PlanLimitMiddleware>();
app.UseAuthorization();
app.MapControllers();
app.MapHub<KdsHub>("/hubs/kds");
app.MapHub<GuestOrderHub>("/hubs/guest-order");
app.MapGet("/health", () => Results.Ok(new { status = "healthy" }));
if (!app.Configuration.GetValue<bool>("Testing:Enabled"))
{
app.UseHangfireDashboard("/hangfire");
RecurringJob.AddOrUpdate<SubscriptionRenewalReminderJob>(
"subscription-renewal-reminder",
job => job.ExecuteAsync(),
Cron.Daily(6));
RecurringJob.AddOrUpdate<GenerateYesterdayReportsJob>(
"daily-reports-yesterday",
job => job.ExecuteAsync(),
"5 0 * * *",
new RecurringJobOptions { TimeZone = IranCalendar.TimeZone });
RecurringJob.AddOrUpdate<BranchPermanentDeleteJob>(
"branch-permanent-delete",
job => job.ExecuteAsync(),
Cron.Hourly);
}
return app;
}
}