Files
SVSimServer/SVSim.EmulatedEntrypoint/Program.cs
gamer147 2c62a7be80 refactor(inventory): delete old primitives after InventoryService cutover
Removed RewardGrantService, CurrencySpendService, ICurrencySpendService,
ViewerEntitlements, IViewerEntitlements, CardAcquisitionService,
ICardAcquisitionService, CardGrantResult and their tests
(RewardGrantServiceTests, CurrencySpendServiceTests,
CardAcquisitionServiceTests, ViewerEntitlementsTests). Removed four DI
registrations from Program.cs. No caller references any deleted type;
GrantedReward and EffectiveCosmetics were pre-moved to InventoryGrantTypes.cs
in the prior commit. Build clean, 712/712 tests pass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 17:07:30 -04:00

172 lines
8.9 KiB
C#

using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.EntityFrameworkCore;
using SVSim.Database;
using SVSim.Database.Repositories.BuildDeck;
using SVSim.Database.Repositories.Card;
using SVSim.Database.Repositories.Collectibles;
using SVSim.Database.Repositories.Deck;
using SVSim.Database.Repositories.Globals;
using SVSim.Database.Repositories.Pack;
using SVSim.Database.Repositories.Story;
using SVSim.Database.Repositories.Viewer;
using SVSim.Database.Services;
using SVSim.EmulatedEntrypoint.Configuration;
using SVSim.EmulatedEntrypoint.Extensions;
using SVSim.EmulatedEntrypoint.Middlewares;
using SVSim.EmulatedEntrypoint.Security.SteamSessionAuthentication;
using SVSim.EmulatedEntrypoint.Services;
namespace SVSim.EmulatedEntrypoint;
public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers().AddJsonOptions(opt =>
{
// Wire-format congruence: the encrypted msgpack path uses snake_case [Key("...")]
// names; the plain-JSON path runs through System.Text.Json. Match them by using
// SnakeCaseLower naming policy here so both paths emit identical key names — and
// so the translation middleware can hand JSON keys straight through to msgpack
// without per-property name remapping.
opt.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower;
// Production omits null/optional fields entirely; the client uses
// `Keys.Contains(name)` as a presence check and calls `.ToInt()` (etc.) on the
// value without a null guard. Emitting `"key":null` makes Contains return true and
// crashes the client. Drop nulls during serialization so missing == absent.
opt.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
// Format-typed properties serialize to/from the wire deck_format int via the
// client's FormatConvertApi mapping. See FormatExtensions.cs.
opt.JsonSerializerOptions.Converters.Add(new FormatJsonConverter());
});
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c =>
{
// Disambiguate same-named DTOs across families (e.g. Story.StartRequest vs
// BasicPuzzle.StartRequest) by qualifying schema ids with the full type name.
c.CustomSchemaIds(t => t.FullName?.Replace("+", "."));
});
builder.Services.AddHttpLogging(opt =>
{
});
builder.Services.Configure<DeckOptions>(builder.Configuration.GetSection(DeckOptions.SectionName));
#region Database Services
builder.Services.AddDbContext<SVSimDbContext>(opt =>
{
opt.UseNpgsql(builder.Configuration.GetConnectionString("ApplicationDb"));
});
builder.Services.AddTransient<IViewerRepository, ViewerRepository>();
builder.Services.AddTransient<IPuzzleClearRepository, PuzzleClearRepository>();
builder.Services.AddTransient<ICardRepository, CardRepository>();
builder.Services.AddTransient<ICardInventoryRepository, CardInventoryRepository>();
builder.Services.AddTransient<ICollectionRepository, CollectionRepository>();
builder.Services.AddTransient<IGlobalsRepository, GlobalsRepository>();
builder.Services.AddTransient<IPuzzleCatalogRepository, PuzzleCatalogRepository>();
builder.Services.AddTransient<IArenaTwoPickRewardRepository, ArenaTwoPickRewardRepository>();
builder.Services.AddTransient<IDeckRepository, DeckRepository>();
builder.Services.AddTransient<IPackRepository, PackRepository>();
builder.Services.AddScoped<SVSim.Database.Repositories.PackDrawTables.IPackDrawTableRepository, SVSim.Database.Repositories.PackDrawTables.PackDrawTableRepository>();
builder.Services.AddTransient<IBuildDeckRepository, BuildDeckRepository>();
// Scoped (not Singleton) to avoid the singleton-depends-on-scoped-DbContext lifecycle
// pitfall. Cost: one indexed single-row query per section per request — trivial. No
// in-process cache today; the IGameConfigService interface is shaped to allow one later.
builder.Services.AddScoped<SVSim.Database.Services.IGameConfigService, GameConfigService>();
builder.Services.AddScoped<ICardFoilLookup, DbCardFoilLookup>();
builder.Services.AddScoped<PackOpenService>();
builder.Services.AddScoped<IGachaPointService, GachaPointService>();
builder.Services.AddScoped<SVSim.Database.Services.Inventory.IInventoryService,
SVSim.Database.Services.Inventory.InventoryService>();
builder.Services.AddScoped<SVSim.Database.Repositories.BattlePass.IBattlePassRepository,
SVSim.Database.Repositories.BattlePass.BattlePassRepository>();
builder.Services.AddScoped<SVSim.Database.Repositories.BattlePass.IViewerBattlePassRepository,
SVSim.Database.Repositories.BattlePass.ViewerBattlePassRepository>();
builder.Services.AddScoped<IBattlePassService, BattlePassService>();
builder.Services.AddScoped<SVSim.Database.Repositories.Mission.IMissionCatalogRepository,
SVSim.Database.Repositories.Mission.MissionCatalogRepository>();
builder.Services.AddScoped<SVSim.Database.Repositories.Mission.IViewerMissionRepository,
SVSim.Database.Repositories.Mission.ViewerMissionRepository>();
builder.Services.AddScoped<IMissionProgressService, MissionProgressService>();
builder.Services.AddScoped<IViewerMissionStateService, ViewerMissionStateService>();
builder.Services.AddScoped<IMissionAssembler, MissionAssembler>();
builder.Services.AddSingleton<TimeProvider>(TimeProvider.System);
builder.Services.AddScoped<IStoryMasterRepository, StoryMasterRepository>();
builder.Services.AddScoped<IViewerStoryProgressRepository, ViewerStoryProgressRepository>();
builder.Services.AddScoped<IArenaTwoPickRunRepository, ArenaTwoPickRunRepository>();
builder.Services.AddScoped<IArenaTwoPickCardPoolService, ArenaTwoPickCardPoolService>();
builder.Services.AddScoped<IArenaTwoPickService, ArenaTwoPickService>();
builder.Services.AddScoped<IStoryService, StoryService>();
builder.Services.AddScoped<IDeckListBuilder, DeckListBuilder>();
builder.Services.AddSingleton<IRandom, SystemRandom>();
builder.Services.AddSingleton<PuzzleMissionEvaluator>();
// Deck-code mint/resolve uses IMemoryCache for ephemeral (3-min TTL) storage; no DB
// row, no migration. Singleton because the cache + RNG seam are process-wide.
builder.Services.AddMemoryCache();
builder.Services.AddSingleton<IDeckCodeService, DeckCodeService>();
#endregion
builder.Services.AddTransient<ShadowverseTranslationMiddleware>();
builder.Services.AddTransient<SessionidMappingMiddleware>();
builder.Services.AddSingleton<ShadowverseSessionService>();
builder.Services.AddSingleton<ISteamServer, FacepunchSteamServer>();
builder.Services.AddSingleton<SteamSessionService>();
builder.Services.AddAuthentication()
.AddScheme<SteamAuthenticationHandlerOptions, SteamSessionAuthenticationHandler>(
SteamAuthenticationConstants.SchemeName,
opt =>
{
});
var app = builder.Build();
// Update database (skipped for non-relational providers, e.g. InMemory in tests, and
// skipped under the "Testing" environment where the test fixture has already called
// EnsureCreated against a SQLite in-memory DB — the Postgres migrations would fail there).
using (var scope = app.Services.CreateScope())
{
var dbContext = scope.ServiceProvider.GetRequiredService<SVSimDbContext>();
if (dbContext.Database.IsRelational() && !app.Environment.IsEnvironment("Testing"))
{
dbContext.UpdateDatabase();
dbContext.EnsureSeedDataAsync().GetAwaiter().GetResult();
}
}
app.UseHttpLogging();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
//app.UseHttpsRedirection();
app.UseMiddleware<SessionidMappingMiddleware>();
app.UseMiddleware<ShadowverseTranslationMiddleware>();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
}
}