feat(config): persist is_foil_preferred + is_prize_preferred (stubs → real)

This commit is contained in:
gamer147
2026-06-13 09:54:51 -04:00
parent 911374106f
commit 13ca7d118e
8 changed files with 4783 additions and 13 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,40 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace SVSim.Database.Migrations
{
/// <inheritdoc />
public partial class ConfigPreferenceFlags : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "Info_IsFoilPreferred",
table: "Viewers",
type: "boolean",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<bool>(
name: "Info_IsPrizePreferred",
table: "Viewers",
type: "boolean",
nullable: false,
defaultValue: false);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Info_IsFoilPreferred",
table: "Viewers");
migrationBuilder.DropColumn(
name: "Info_IsPrizePreferred",
table: "Viewers");
}
}
}

View File

@@ -4340,12 +4340,18 @@ namespace SVSim.Database.Migrations
.IsRequired()
.HasColumnType("text");
b1.Property<bool>("IsFoilPreferred")
.HasColumnType("boolean");
b1.Property<bool>("IsOfficial")
.HasColumnType("boolean");
b1.Property<bool>("IsOfficialMarkDisplayed")
.HasColumnType("boolean");
b1.Property<bool>("IsPrizePreferred")
.HasColumnType("boolean");
b1.Property<int>("MaxFriends")
.HasColumnType("integer");

View File

@@ -10,6 +10,8 @@ public class ViewerInfo
public int MaxFriends { get; set; }
public bool IsOfficial { get; set; }
public bool IsOfficialMarkDisplayed { get; set; }
public bool IsFoilPreferred { get; set; }
public bool IsPrizePreferred { get; set; }
#region Navigation Properties

View File

@@ -1,31 +1,42 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using SVSim.Database;
using SVSim.EmulatedEntrypoint.Models.Dtos.Common;
using SVSim.EmulatedEntrypoint.Models.Dtos.Requests;
namespace SVSim.EmulatedEntrypoint.Controllers;
/// <summary>
/// /config/* — viewer-scoped preference toggles. Fired by the client as fire-and-forget
/// after the deck-builder save flow (and from settings screens). Currently acknowledge-only:
/// UserConfig is stubbed in load/index + mypage/index until a viewer-config persistence
/// layer lands. Mirrors the set_deck_redis precedent (no-op accept).
/// /config/* — viewer-scoped preference toggles. Persists to ViewerInfo; reads back through
/// /load/index + /mypage/index user_config.
/// </summary>
public class ConfigController : SVSimController
{
private readonly SVSimDbContext _db;
public ConfigController(SVSimDbContext db) => _db = db;
[HttpPost("update_foil_preferred")]
public ActionResult<EmptyResponse> UpdateFoilPreferred(ConfigUpdateFoilPreferredRequest request)
public async Task<ActionResult<EmptyResponse>> UpdateFoilPreferred(
ConfigUpdateFoilPreferredRequest request, CancellationToken ct)
{
if (!TryGetViewerId(out long _)) return Unauthorized();
// TODO(viewer-config-persist): write request.IsFoilPreferred onto the viewer's config row
// and read it back in load/index + mypage/index instead of `new UserConfig()`.
if (!TryGetViewerId(out long viewerId)) return Unauthorized();
var viewer = await _db.Viewers.FirstOrDefaultAsync(v => v.Id == viewerId, ct);
if (viewer is null) return Unauthorized();
viewer.Info.IsFoilPreferred = request.IsFoilPreferred != 0;
await _db.SaveChangesAsync(ct);
return new EmptyResponse();
}
[HttpPost("update_prize_preferred")]
public ActionResult<EmptyResponse> UpdatePrizePreferred(ConfigUpdatePrizePreferredRequest request)
public async Task<ActionResult<EmptyResponse>> UpdatePrizePreferred(
ConfigUpdatePrizePreferredRequest request, CancellationToken ct)
{
if (!TryGetViewerId(out long _)) return Unauthorized();
// TODO(viewer-config-persist): see UpdateFoilPreferred.
if (!TryGetViewerId(out long viewerId)) return Unauthorized();
var viewer = await _db.Viewers.FirstOrDefaultAsync(v => v.Id == viewerId, ct);
if (viewer is null) return Unauthorized();
viewer.Info.IsPrizePreferred = request.IsPrizePreferred != 0;
await _db.SaveChangesAsync(ct);
return new EmptyResponse();
}
}

View File

@@ -253,7 +253,11 @@ public class LoadController : SVSimController
},
ArenaInfos = await BuildArenaInfosAsync(viewer.Id),
RotationSets = rotationSets,
UserConfig = new UserConfig(),
UserConfig = new UserConfig
{
IsFoilPreferred = viewer.Info.IsFoilPreferred ? 1 : 0,
IsPrizePreferred = viewer.Info.IsPrizePreferred ? 1 : 0,
},
OpenBattlefieldIds = (await _globalsRepository.GetBattlefields(true))
.Select(bf => bf.Id.ToString()).ToList(),
DefaultSettings = new DefaultSettings(defaultLoadout),

View File

@@ -111,7 +111,11 @@ public class MyPageController : SVSimController
IsJoinTournament = false,
IsAdminWatchUser = false,
},
UserConfig = new UserConfig(), // TODO(mypage-stub): persist viewer UserConfig
UserConfig = new UserConfig
{
IsFoilPreferred = viewer.Info.IsFoilPreferred ? 1 : 0,
IsPrizePreferred = viewer.Info.IsPrizePreferred ? 1 : 0,
},
Quest = new Quest(), // TODO(mypage-stub): active Quest event + viewer flags
MasterPointRankingPeriod = BuildMasterPointRankingPeriod(masterPointPeriod),
PreReleaseStatus = 0, // TODO(mypage-stub): derive from PreReleaseInfo

View File

@@ -0,0 +1,56 @@
using System.Net;
using System.Text;
using System.Text.Json;
using NUnit.Framework;
using SVSim.UnitTests.Infrastructure;
namespace SVSim.UnitTests.Controllers;
public class ConfigControllerPreferencesTests
{
[Test]
public async Task UpdateFoilPreferred_persists_and_appears_in_load_index()
{
using var factory = new SVSimTestFactory();
long viewerId = await factory.SeedViewerAsync(tutorialState: 0);
using var client = factory.CreateAuthenticatedClient(viewerId);
var setJson = """{"is_foil_preferred":1,"viewer_id":"0","steam_id":0,"steam_session_ticket":""}""";
var setResp = await client.PostAsync("/config/update_foil_preferred",
new StringContent(setJson, Encoding.UTF8, "application/json"));
var setBody = await setResp.Content.ReadAsStringAsync();
Assert.That(setResp.StatusCode, Is.EqualTo(HttpStatusCode.OK), setBody);
var loadJson = """{"viewer_id":"0","steam_id":0,"steam_session_ticket":"","carrier":"none","card_master_hash":""}""";
var loadResp = await client.PostAsync("/load/index",
new StringContent(loadJson, Encoding.UTF8, "application/json"));
var body = await loadResp.Content.ReadAsStringAsync();
Assert.That(loadResp.StatusCode, Is.EqualTo(HttpStatusCode.OK), body);
using var doc = JsonDocument.Parse(body);
var cfg = doc.RootElement.GetProperty("user_config");
Assert.That(cfg.GetProperty("is_foil_preferred").GetInt32(), Is.EqualTo(1), body);
}
[Test]
public async Task UpdatePrizePreferred_persists_and_appears_in_load_index()
{
using var factory = new SVSimTestFactory();
long viewerId = await factory.SeedViewerAsync(tutorialState: 0);
using var client = factory.CreateAuthenticatedClient(viewerId);
var setJson = """{"is_prize_preferred":1,"viewer_id":"0","steam_id":0,"steam_session_ticket":""}""";
var setResp = await client.PostAsync("/config/update_prize_preferred",
new StringContent(setJson, Encoding.UTF8, "application/json"));
Assert.That(setResp.StatusCode, Is.EqualTo(HttpStatusCode.OK));
var loadJson = """{"viewer_id":"0","steam_id":0,"steam_session_ticket":"","carrier":"none","card_master_hash":""}""";
var loadResp = await client.PostAsync("/load/index",
new StringContent(loadJson, Encoding.UTF8, "application/json"));
var body = await loadResp.Content.ReadAsStringAsync();
using var doc = JsonDocument.Parse(body);
var cfg = doc.RootElement.GetProperty("user_config");
Assert.That(cfg.GetProperty("is_prize_preferred").GetInt32(), Is.EqualTo(1));
}
}