[Timestamp] byte[] doesn't work under SQLite (the test backend) — EF expects the DB to populate it on insert, but SQLite has no equivalent of Postgres's xmin. The WHERE Status = Unclaimed filter plus IInventoryService's viewer-level concurrency is the practical defense; RowVersion was only a backstop. Regenerated the migration without the RowVersion column. Wire reward_type on the gift endpoint uses a gift-specific scheme that diverges from UserGoodsType for currencies: wire 1 = Crystal (enum=2), wire 9 = Rupy (enum=9), wire 4 = Item (enum=4). A naked cast resolves wire 1 to UserGoodsType.RedEther and silently grants the wrong wallet — restored the explicit WireRewardTypeToUserGoodsType map from the old tutorial controller. Retrofits existing GiftControllerTests to call SeedTutorialPresentsAsync on the new helper (RegisterViewer doesn't auto-seed; only the prod signup path does). All 7 existing tests pass.
49 lines
1.7 KiB
C#
49 lines
1.7 KiB
C#
namespace SVSim.Database.Models;
|
|
|
|
/// <summary>
|
|
/// One row per gift in a viewer's inbox. Replaces the tutorial-only
|
|
/// <c>ViewerClaimedTutorialGift</c> receipts model with a unified status-enum row that
|
|
/// serves both /gift/top + /gift/receive_gift (prod) and /tutorial/gift_top +
|
|
/// /tutorial/gift_receive (tutorial alias).
|
|
/// </summary>
|
|
public class ViewerPresent
|
|
{
|
|
public long Id { get; set; }
|
|
|
|
public long ViewerId { get; set; }
|
|
public Viewer Viewer { get; set; } = null!;
|
|
|
|
/// <summary>Wire id ("71409625" in the prod capture). String to match the wire.</summary>
|
|
public string PresentId { get; set; } = string.Empty;
|
|
|
|
public PresentStatus Status { get; set; }
|
|
|
|
/// <summary>UserGoodsType-compatible int. Wire is stringified — see PresentMapper.</summary>
|
|
public int RewardType { get; set; }
|
|
public long RewardDetailId { get; set; }
|
|
public long RewardCount { get; set; }
|
|
public int ConditionNumber { get; set; }
|
|
public int PresentLimitType { get; set; }
|
|
public long RewardLimitTime { get; set; }
|
|
public int? ItemType { get; set; }
|
|
public string Message { get; set; } = string.Empty;
|
|
|
|
public DateTime CreatedAt { get; set; }
|
|
public DateTime? ClaimedAt { get; set; }
|
|
|
|
/// <summary>
|
|
/// Free-form provenance tag for future producers ("tutorial", "challenge_win",
|
|
/// "payment_refund:<txid>", "event:<id>"). Nothing in the receive handler reads
|
|
/// this today — the tutorial-step advance is route-gated, not Source-gated.
|
|
/// </summary>
|
|
public string? Source { get; set; }
|
|
}
|
|
|
|
public enum PresentStatus : byte
|
|
{
|
|
Unclaimed = 0,
|
|
Claimed = 1,
|
|
Deleted = 2,
|
|
Expired = 3,
|
|
}
|