feat(config): persist is_foil_preferred + is_prize_preferred (stubs → real)
This commit is contained in:
4647
SVSim.Database/Migrations/20260613134356_ConfigPreferenceFlags.Designer.cs
generated
Normal file
4647
SVSim.Database/Migrations/20260613134356_ConfigPreferenceFlags.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4340,12 +4340,18 @@ namespace SVSim.Database.Migrations
|
|||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("text");
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<bool>("IsFoilPreferred")
|
||||||
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
b1.Property<bool>("IsOfficial")
|
b1.Property<bool>("IsOfficial")
|
||||||
.HasColumnType("boolean");
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
b1.Property<bool>("IsOfficialMarkDisplayed")
|
b1.Property<bool>("IsOfficialMarkDisplayed")
|
||||||
.HasColumnType("boolean");
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
|
b1.Property<bool>("IsPrizePreferred")
|
||||||
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
b1.Property<int>("MaxFriends")
|
b1.Property<int>("MaxFriends")
|
||||||
.HasColumnType("integer");
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ public class ViewerInfo
|
|||||||
public int MaxFriends { get; set; }
|
public int MaxFriends { get; set; }
|
||||||
public bool IsOfficial { get; set; }
|
public bool IsOfficial { get; set; }
|
||||||
public bool IsOfficialMarkDisplayed { get; set; }
|
public bool IsOfficialMarkDisplayed { get; set; }
|
||||||
|
public bool IsFoilPreferred { get; set; }
|
||||||
|
public bool IsPrizePreferred { get; set; }
|
||||||
|
|
||||||
#region Navigation Properties
|
#region Navigation Properties
|
||||||
|
|
||||||
|
|||||||
@@ -1,31 +1,42 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using SVSim.Database;
|
||||||
using SVSim.EmulatedEntrypoint.Models.Dtos.Common;
|
using SVSim.EmulatedEntrypoint.Models.Dtos.Common;
|
||||||
using SVSim.EmulatedEntrypoint.Models.Dtos.Requests;
|
using SVSim.EmulatedEntrypoint.Models.Dtos.Requests;
|
||||||
|
|
||||||
namespace SVSim.EmulatedEntrypoint.Controllers;
|
namespace SVSim.EmulatedEntrypoint.Controllers;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// /config/* — viewer-scoped preference toggles. Fired by the client as fire-and-forget
|
/// /config/* — viewer-scoped preference toggles. Persists to ViewerInfo; reads back through
|
||||||
/// after the deck-builder save flow (and from settings screens). Currently acknowledge-only:
|
/// /load/index + /mypage/index user_config.
|
||||||
/// UserConfig is stubbed in load/index + mypage/index until a viewer-config persistence
|
|
||||||
/// layer lands. Mirrors the set_deck_redis precedent (no-op accept).
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ConfigController : SVSimController
|
public class ConfigController : SVSimController
|
||||||
{
|
{
|
||||||
|
private readonly SVSimDbContext _db;
|
||||||
|
|
||||||
|
public ConfigController(SVSimDbContext db) => _db = db;
|
||||||
|
|
||||||
[HttpPost("update_foil_preferred")]
|
[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();
|
if (!TryGetViewerId(out long viewerId)) return Unauthorized();
|
||||||
// TODO(viewer-config-persist): write request.IsFoilPreferred onto the viewer's config row
|
var viewer = await _db.Viewers.FirstOrDefaultAsync(v => v.Id == viewerId, ct);
|
||||||
// and read it back in load/index + mypage/index instead of `new UserConfig()`.
|
if (viewer is null) return Unauthorized();
|
||||||
|
viewer.Info.IsFoilPreferred = request.IsFoilPreferred != 0;
|
||||||
|
await _db.SaveChangesAsync(ct);
|
||||||
return new EmptyResponse();
|
return new EmptyResponse();
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("update_prize_preferred")]
|
[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();
|
if (!TryGetViewerId(out long viewerId)) return Unauthorized();
|
||||||
// TODO(viewer-config-persist): see UpdateFoilPreferred.
|
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();
|
return new EmptyResponse();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -253,7 +253,11 @@ public class LoadController : SVSimController
|
|||||||
},
|
},
|
||||||
ArenaInfos = await BuildArenaInfosAsync(viewer.Id),
|
ArenaInfos = await BuildArenaInfosAsync(viewer.Id),
|
||||||
RotationSets = rotationSets,
|
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))
|
OpenBattlefieldIds = (await _globalsRepository.GetBattlefields(true))
|
||||||
.Select(bf => bf.Id.ToString()).ToList(),
|
.Select(bf => bf.Id.ToString()).ToList(),
|
||||||
DefaultSettings = new DefaultSettings(defaultLoadout),
|
DefaultSettings = new DefaultSettings(defaultLoadout),
|
||||||
|
|||||||
@@ -111,7 +111,11 @@ public class MyPageController : SVSimController
|
|||||||
IsJoinTournament = false,
|
IsJoinTournament = false,
|
||||||
IsAdminWatchUser = 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
|
Quest = new Quest(), // TODO(mypage-stub): active Quest event + viewer flags
|
||||||
MasterPointRankingPeriod = BuildMasterPointRankingPeriod(masterPointPeriod),
|
MasterPointRankingPeriod = BuildMasterPointRankingPeriod(masterPointPeriod),
|
||||||
PreReleaseStatus = 0, // TODO(mypage-stub): derive from PreReleaseInfo
|
PreReleaseStatus = 0, // TODO(mypage-stub): derive from PreReleaseInfo
|
||||||
|
|||||||
@@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user