import_viewer: round-trip ViewerAchievement + counter

This commit is contained in:
gamer147
2026-06-23 19:54:56 -04:00
parent c253930d59
commit 2cb254553f
3 changed files with 230 additions and 0 deletions

View File

@@ -81,6 +81,7 @@ public class AdminController : SVSimController
.Include(v => v.Currency)
.Include(v => v.MissionData)
.Include(v => v.Missions)
.Include(v => v.Achievements)
.Include(v => v.Classes).ThenInclude(c => c.Class)
.Include(v => v.Sleeves)
.Include(v => v.Emblems)
@@ -341,6 +342,81 @@ public class AdminController : SVSimController
}
}
// Pass C: Achievements + ViewerEventCounter
var skippedAchievementTypes = new HashSet<int>();
var skippedAchievementCounterTypes = new HashSet<int>();
if (request.Achievements is { } achievements)
{
viewer.Achievements.Clear();
var types = achievements.Select(a => a.AchievementType).Distinct().ToList();
var catalogs = await _dbContext.AchievementCatalog
.Where(c => types.Contains(c.AchievementType))
.GroupBy(c => c.AchievementType)
.ToDictionaryAsync(g => g.Key, g => g.First());
// Resolve counter keys for all achievements up-front so we can load existing counters
// in one query and avoid duplicate-Add races when two achievements share the same
// EventType (same (EventKey, AllTime) cache key) within the same import batch.
var resolvedAchievementCounterKeys = new Dictionary<int, (string EventKey, string Period)>();
foreach (var a in achievements)
{
if (!catalogs.TryGetValue(a.AchievementType, out var cat)) continue;
var r = ResolveAchievementCounter(cat);
if (r is not null)
resolvedAchievementCounterKeys[a.AchievementType] = r.Value;
}
// Collect all distinct (EventKey, Period) pairs we will touch.
var allAchEventKeys = resolvedAchievementCounterKeys.Values.Select(v => v.EventKey).Distinct().ToList();
var allAchPeriods = resolvedAchievementCounterKeys.Values.Select(v => v.Period).Distinct().ToList();
// Load existing counters in one query.
var existingAchCounters = await _dbContext.ViewerEventCounters
.Where(c => c.ViewerId == viewer.Id
&& allAchEventKeys.Contains(c.EventKey)
&& allAchPeriods.Contains(c.Period))
.ToListAsync();
var achievementCounterCache = existingAchCounters
.ToDictionary(c => (c.EventKey, c.Period));
foreach (var a in achievements)
{
if (!catalogs.TryGetValue(a.AchievementType, out var cat))
{
skippedAchievementTypes.Add(a.AchievementType);
continue;
}
viewer.Achievements.Add(new ViewerAchievement
{
ViewerId = viewer.Id,
AchievementType = a.AchievementType,
Level = a.Level,
NowAchievedLevel = a.NowAchievedLevel,
ResultAnnounceSawLevel = a.ResultAnnounceSawLevel,
AchievementStatus = 0
});
if (!resolvedAchievementCounterKeys.TryGetValue(a.AchievementType, out var resolved))
{
skippedAchievementCounterTypes.Add(a.AchievementType);
continue;
}
var (eventKey, period) = resolved;
if (!achievementCounterCache.TryGetValue((eventKey, period), out var counter))
{
counter = new ViewerEventCounter
{
ViewerId = viewer.Id,
EventKey = eventKey,
Period = period,
};
_dbContext.ViewerEventCounters.Add(counter);
achievementCounterCache[(eventKey, period)] = counter;
}
counter.Count = a.TotalCount;
}
}
await _dbContext.SaveChangesAsync();
if (skippedCardIds.Count > 0)
@@ -365,6 +441,20 @@ public class AdminController : SVSimController
request.SteamId, viewer.Id, skippedMissionCounterIds.Count,
string.Join(", ", skippedMissionCounterIds.Take(10)));
}
if (skippedAchievementTypes.Count > 0)
{
_logger.LogWarning(
"ImportViewer (steam_id={SteamId}, viewer_id={ViewerId}): skipped {Count} unknown achievement_type(s). Sample: [{Sample}]",
request.SteamId, viewer.Id, skippedAchievementTypes.Count,
string.Join(", ", skippedAchievementTypes.Take(10)));
}
if (skippedAchievementCounterTypes.Count > 0)
{
_logger.LogWarning(
"ImportViewer (steam_id={SteamId}, viewer_id={ViewerId}): {Count} achievement(s) had unresolvable EventKey, counter not written. Sample: [{Sample}]",
request.SteamId, viewer.Id, skippedAchievementCounterTypes.Count,
string.Join(", ", skippedAchievementCounterTypes.Take(10)));
}
return new ImportViewerResponse
{
@@ -374,6 +464,8 @@ public class AdminController : SVSimController
SkippedCardCount = skippedCardIds.Count,
SkippedMissionCount = skippedMissionIds.Count,
SkippedMissionCounterCount = skippedMissionCounterIds.Count,
SkippedAchievementCount = skippedAchievementTypes.Count,
SkippedAchievementCounterCount = skippedAchievementCounterTypes.Count,
};
}
@@ -406,6 +498,13 @@ public class AdminController : SVSimController
return (catalog.EventType!, period);
}
// TODO: unify with MissionAssembler.cs — same logic duplicated here.
private static (string EventKey, string Period)? ResolveAchievementCounter(AchievementCatalogEntry catalog)
{
if (string.IsNullOrEmpty(catalog.EventType)) return null;
return (catalog.EventType!, JstPeriod.AllTime);
}
/// <summary>
/// Fallback sleeve id used when an imported deck has no resolvable <c>sleeve_id</c>.
/// 3000011 is prod's default deck sleeve.