import_viewer: round-trip ViewerMission + ViewerEventCounter

This commit is contained in:
gamer147
2026-06-23 19:39:24 -04:00
parent d53628f8cc
commit 22778b65fd
3 changed files with 201 additions and 0 deletions

View File

@@ -10,6 +10,7 @@ using SVSim.EmulatedEntrypoint.Extensions;
using SVSim.EmulatedEntrypoint.Infrastructure;
using SVSim.EmulatedEntrypoint.Models.Dtos.Requests.Admin;
using SVSim.EmulatedEntrypoint.Models.Dtos.Responses.Admin;
using SVSim.EmulatedEntrypoint.Services;
namespace SVSim.EmulatedEntrypoint.Controllers;
@@ -79,6 +80,7 @@ public class AdminController : SVSimController
.Include(v => v.Info).ThenInclude(i => i.SelectedDegree)
.Include(v => v.Currency)
.Include(v => v.MissionData)
.Include(v => v.Missions)
.Include(v => v.Classes).ThenInclude(c => c.Class)
.Include(v => v.Sleeves)
.Include(v => v.Emblems)
@@ -265,6 +267,60 @@ public class AdminController : SVSimController
viewer.MissionData.MissionChangeTime = DateTimeOffset.FromUnixTimeSeconds(mct).UtcDateTime;
}
// Pass B: Missions + ViewerEventCounter
var skippedMissionIds = new HashSet<int>();
var skippedMissionCounterIds = new HashSet<int>();
if (request.Missions is { } missions)
{
viewer.Missions.Clear();
var missionIds = missions.Select(m => m.MissionId).Distinct().ToList();
var catalogs = await _dbContext.MissionCatalog
.Where(c => missionIds.Contains(c.Id))
.ToDictionaryAsync(c => c.Id);
var nowUtc = DateTimeOffset.UtcNow;
foreach (var m in missions)
{
if (!catalogs.TryGetValue(m.MissionId, out var cat))
{
skippedMissionIds.Add(m.MissionId);
continue;
}
viewer.Missions.Add(new ViewerMission
{
MissionCatalogId = cat.Id,
Slot = cat.LotType == 6 ? 0 : 1,
MissionStatus = m.MissionStatus,
AssignedAt = DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
ClaimedAt = null
});
var resolved = ResolveMissionCounter(cat, nowUtc);
if (resolved is null)
{
skippedMissionCounterIds.Add(m.MissionId);
continue;
}
var (eventKey, period) = resolved.Value;
var existingCounter = await _dbContext.ViewerEventCounters
.FirstOrDefaultAsync(c => c.ViewerId == viewer.Id && c.EventKey == eventKey && c.Period == period);
if (existingCounter is null)
{
_dbContext.ViewerEventCounters.Add(new ViewerEventCounter
{
ViewerId = viewer.Id,
EventKey = eventKey,
Period = period,
Count = m.TotalCount
});
}
else
{
existingCounter.Count = m.TotalCount;
}
}
}
await _dbContext.SaveChangesAsync();
if (skippedCardIds.Count > 0)
@@ -275,6 +331,20 @@ public class AdminController : SVSimController
request.SteamId, viewer.Id, skippedCardIds.Count,
string.Join(", ", skippedCardIds.Take(20)));
}
if (skippedMissionIds.Count > 0)
{
_logger.LogWarning(
"ImportViewer (steam_id={SteamId}, viewer_id={ViewerId}): skipped {Count} unknown mission_id(s). Sample: [{Sample}]",
request.SteamId, viewer.Id, skippedMissionIds.Count,
string.Join(", ", skippedMissionIds.Take(10)));
}
if (skippedMissionCounterIds.Count > 0)
{
_logger.LogWarning(
"ImportViewer (steam_id={SteamId}, viewer_id={ViewerId}): {Count} mission(s) had unresolvable EventKey, counter not written. Sample: [{Sample}]",
request.SteamId, viewer.Id, skippedMissionCounterIds.Count,
string.Join(", ", skippedMissionCounterIds.Take(10)));
}
return new ImportViewerResponse
{
@@ -282,6 +352,8 @@ public class AdminController : SVSimController
ShortUdid = viewer.ShortUdid,
WasCreated = wasCreated,
SkippedCardCount = skippedCardIds.Count,
SkippedMissionCount = skippedMissionIds.Count,
SkippedMissionCounterCount = skippedMissionCounterIds.Count,
};
}
@@ -300,6 +372,20 @@ public class AdminController : SVSimController
owned.AddRange(rows);
}
// TODO: unify with MissionAssembler.cs — same logic duplicated here.
private static (string EventKey, string Period)? ResolveMissionCounter(MissionCatalogEntry catalog, DateTimeOffset nowUtc)
{
if (string.IsNullOrEmpty(catalog.EventType)) return null;
var period = catalog.LotType switch
{
6 => JstPeriod.DayKey(nowUtc),
2 => JstPeriod.WeekKey(nowUtc),
_ => null
};
if (period is null) return null;
return (catalog.EventType!, period);
}
/// <summary>
/// Fallback sleeve id used when an imported deck has no resolvable <c>sleeve_id</c>.
/// 3000011 is prod's default deck sleeve.