using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; using StackExchange.Redis; using Vanguard.Content; using Vanguard.Rules.Cards; using Vanguard.Server.Api; using Vanguard.Server.Configuration; using Vanguard.Server.Matches; using Vanguard.Server.Matchmaking; using Vanguard.Server.Profiles; using Vanguard.Server.Realtime; using Vanguard.Server.Storage; using Vanguard.Server.Tickets; var builder = WebApplication.CreateBuilder(args); builder.Services.Configure(builder.Configuration.GetSection("Game")); builder.Services.Configure(builder.Configuration.GetSection("Content")); builder.Services.Configure(builder.Configuration.GetSection("Storage")); builder.Services.Configure(builder.Configuration.GetSection("Redis")); builder.Services.AddSingleton(sp => sp.GetRequiredService>().Value); builder.Services.AddSingleton(sp => sp.GetRequiredService>().Value); builder.Services.AddSingleton(sp => sp.GetRequiredService>().Value); builder.Services.AddSingleton(sp => sp.GetRequiredService>().Value); builder.Services.AddSingleton(TimeProvider.System); builder.Services.AddSingleton(sp => { var content = sp.GetRequiredService(); var directory = ContentPaths.Resolve(content.Path); return CatalogLoader.LoadDirectory(directory); }); var storage = builder.Configuration.GetSection("Storage").Get() ?? new StorageOptions(); var storageProvider = storage.Provider?.Trim() ?? "None"; if (storageProvider.Equals("Postgres", StringComparison.OrdinalIgnoreCase)) { if (string.IsNullOrWhiteSpace(storage.ConnectionString)) throw new InvalidOperationException("Storage:ConnectionString обязателен для Provider=Postgres."); builder.Services.AddDbContextFactory(options => options.UseNpgsql(storage.ConnectionString)); builder.Services.AddSingleton(); } else if (storageProvider.Equals("Memory", StringComparison.OrdinalIgnoreCase)) { builder.Services.AddDbContextFactory(options => options.UseInMemoryDatabase("vanguard-matches")); builder.Services.AddSingleton(); } else if (storageProvider.Equals("None", StringComparison.OrdinalIgnoreCase)) { builder.Services.AddSingleton(); } else { throw new InvalidOperationException( $"Неизвестный Storage:Provider «{storageProvider}». Ожидается None, Memory или Postgres."); } var redis = builder.Configuration.GetSection("Redis").Get() ?? new RedisOptions(); if (!string.IsNullOrWhiteSpace(redis.Configuration)) { builder.Services.AddSingleton(_ => { var configuration = ConfigurationOptions.Parse(redis.Configuration); configuration.AbortOnConnectFail = false; return ConnectionMultiplexer.Connect(configuration); }); builder.Services.AddSingleton(); builder.Services.AddSingleton(); } else { builder.Services.AddSingleton(); builder.Services.AddSingleton(); } builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(sp => sp.GetRequiredService()); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddHostedService(); builder.Services.AddHostedService(); var app = builder.Build(); // каталог грузим сразу: сервер без карт бесполезен, лучше упасть на старте app.Services.GetRequiredService(); if (app.Services.GetRequiredService() is EfMatchStore) { var factory = app.Services.GetRequiredService>(); await using var db = await factory.CreateDbContextAsync(); await db.Database.EnsureCreatedAsync(); } // незавершённые матчи поднимаем до того, как сервер начнёт принимать подключения await app.Services.GetRequiredService().RecoverAsync(); await app.Services.GetRequiredService().LoadQueuedAsync(); app.UseWebSockets(new WebSocketOptions { KeepAliveInterval = TimeSpan.FromSeconds(30), }); app.MapGameApi(); app.Map("/game", GameWebSocketEndpoint.HandleAsync); app.Run(); /// Точка входа доступна интеграционным тестам через WebApplicationFactory. public partial class Program { }