feat(import): import owned card collection with unknown-card skip
Extends POST /admin/import_viewer to accept owned_cards (snapshot full-replace). Unknown card_ids are skipped, counted in skipped_card_count on the response, and logged server-side. Count is clamped to OwnedCardEntry.MaxCopies (3). Also injects ILogger into AdminController and adds Cards/Items/Decks to the viewer reload graph. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@ using System.Text.Json;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SVSim.Database;
|
||||
using SVSim.Database.Enums;
|
||||
using SVSim.Database.Models;
|
||||
@@ -20,11 +21,14 @@ public class AdminController : SVSimController
|
||||
{
|
||||
private readonly IViewerRepository _viewerRepository;
|
||||
private readonly SVSimDbContext _dbContext;
|
||||
private readonly ILogger<AdminController> _logger;
|
||||
|
||||
public AdminController(IViewerRepository viewerRepository, SVSimDbContext dbContext)
|
||||
public AdminController(IViewerRepository viewerRepository, SVSimDbContext dbContext,
|
||||
ILogger<AdminController> logger)
|
||||
{
|
||||
_viewerRepository = viewerRepository;
|
||||
_dbContext = dbContext;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -81,6 +85,9 @@ public class AdminController : SVSimController
|
||||
.Include(v => v.Degrees)
|
||||
.Include(v => v.LeaderSkins)
|
||||
.Include(v => v.MyPageBackgrounds)
|
||||
.Include(v => v.Cards).ThenInclude(c => c.Card)
|
||||
.Include(v => v.Items).ThenInclude(i => i.Item)
|
||||
.Include(v => v.Decks).ThenInclude(d => d.Cards)
|
||||
.FirstAsync(v => v.Id == viewerId);
|
||||
|
||||
if (request.DisplayName is not null) viewer.DisplayName = request.DisplayName;
|
||||
@@ -124,6 +131,38 @@ public class AdminController : SVSimController
|
||||
}
|
||||
}
|
||||
|
||||
// Accumulates distinct card_ids referenced by the import (owned list + deck lists)
|
||||
// that aren't in our card master. Surfaced in the response and logged after save.
|
||||
var skippedCardIds = new HashSet<long>();
|
||||
|
||||
if (request.OwnedCards is not null)
|
||||
{
|
||||
var wanted = request.OwnedCards
|
||||
.GroupBy(c => c.CardId)
|
||||
.Select(g => g.First())
|
||||
.ToList();
|
||||
var ids = wanted.Select(c => c.CardId).ToList();
|
||||
var cardMaster = await _dbContext.Cards
|
||||
.Where(c => ids.Contains(c.Id))
|
||||
.ToDictionaryAsync(c => c.Id);
|
||||
|
||||
viewer.Cards.Clear();
|
||||
foreach (var c in wanted)
|
||||
{
|
||||
if (!cardMaster.TryGetValue(c.CardId, out var card))
|
||||
{
|
||||
skippedCardIds.Add(c.CardId);
|
||||
continue;
|
||||
}
|
||||
viewer.Cards.Add(new OwnedCardEntry
|
||||
{
|
||||
Card = card,
|
||||
Count = Math.Clamp(c.Count, 1, OwnedCardEntry.MaxCopies),
|
||||
IsProtected = c.IsProtected,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Clone the 8 starter decks into the viewer when freshly created — workaround for a
|
||||
// client-side NRE in the deck-edit menu (DeckListUI.IsVisibleCreateNewButton at
|
||||
// decompile Wizard/DeckListUI.cs:316 unconditionally reads `_deckGroup.DeckFormat`, but
|
||||
@@ -138,11 +177,21 @@ public class AdminController : SVSimController
|
||||
|
||||
await _dbContext.SaveChangesAsync();
|
||||
|
||||
if (skippedCardIds.Count > 0)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"ImportViewer (steam_id={SteamId}, viewer_id={ViewerId}): skipped {Count} unknown " +
|
||||
"card_id(s) not present in the card master. Sample: [{Sample}]",
|
||||
request.SteamId, viewer.Id, skippedCardIds.Count,
|
||||
string.Join(", ", skippedCardIds.Take(20)));
|
||||
}
|
||||
|
||||
return new ImportViewerResponse
|
||||
{
|
||||
ViewerId = viewer.Id,
|
||||
ShortUdid = viewer.ShortUdid,
|
||||
WasCreated = wasCreated
|
||||
WasCreated = wasCreated,
|
||||
SkippedCardCount = skippedCardIds.Count,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user