fix(import): tolerate numeric my_rotation_id; skip empty deck slots

A real /load/index dump emits my_rotation_id as a bare number (0) for
unset MyRotation slots, which 400'd against the string? DTO field
(AllowReadingFromString only covers string->number). FlexibleStringConverter
accepts either form. Also skip empty deck slots (no cards) on import — a
dump carries every slot, mostly empty placeholders the client manages
itself.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
gamer147
2026-05-29 21:03:10 -04:00
parent 06108e4b6f
commit f754ef1ad3
4 changed files with 91 additions and 1 deletions

View File

@@ -0,0 +1,29 @@
using System.Globalization;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace SVSim.EmulatedEntrypoint.Models.Dtos.Common;
/// <summary>
/// Reads a JSON string OR number as a nullable string, tolerating prod's polymorphic id fields.
/// <c>rotation_id</c> on a /load/index <c>UserDeck</c> is a numeric string ("10008") for real
/// MyRotation decks but a bare number (<c>0</c>) for unset slots — and the global
/// <c>AllowReadingFromString</c> only covers the string→number direction, not number→string, so a
/// plain <c>string?</c> property 400s on the numeric form. Null stays null; numbers serialize via
/// invariant culture so a captured <c>0</c> round-trips to <c>"0"</c>.
/// </summary>
public sealed class FlexibleStringConverter : JsonConverter<string?>
{
public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) =>
reader.TokenType switch
{
JsonTokenType.Null => null,
JsonTokenType.String => reader.GetString(),
JsonTokenType.Number when reader.TryGetInt64(out var n) => n.ToString(CultureInfo.InvariantCulture),
JsonTokenType.Number => reader.GetDouble().ToString(CultureInfo.InvariantCulture),
_ => throw new JsonException($"Unexpected token {reader.TokenType} for a string-or-number field.")
};
public override void Write(Utf8JsonWriter writer, string? value, JsonSerializerOptions options) =>
writer.WriteStringValue(value);
}

View File

@@ -1,4 +1,5 @@
using System.Text.Json.Serialization;
using SVSim.EmulatedEntrypoint.Models.Dtos.Common;
namespace SVSim.EmulatedEntrypoint.Models.Dtos.Requests.Admin;
@@ -44,7 +45,11 @@ public class ImportDeck
[JsonPropertyName("sleeve_id")] public long? SleeveId { get; set; }
[JsonPropertyName("leader_skin_id")] public int? LeaderSkinId { get; set; }
[JsonPropertyName("is_random_leader_skin")] public int? IsRandomLeaderSkin { get; set; }
[JsonPropertyName("my_rotation_id")] public string? MyRotationId { get; set; }
// Prod emits rotation_id as a numeric string ("10008") for real MyRotation decks but a bare
// number (0) for unset slots; FlexibleStringConverter accepts either (a plain string? 400s on
// the numeric form because AllowReadingFromString only covers string→number).
[JsonPropertyName("my_rotation_id")] [JsonConverter(typeof(FlexibleStringConverter))]
public string? MyRotationId { get; set; }
}
public class ImportCurrency