196 lines
8.5 KiB
C#
196 lines
8.5 KiB
C#
using Microsoft.EntityFrameworkCore;
|
|
using Vanguard.Server.Meta;
|
|
|
|
namespace Vanguard.Server.Storage;
|
|
|
|
/// <summary>
|
|
/// Единый контекст игры и мета-данных: матчи, пользователи, коллекция,
|
|
/// колоды, экономика, история. Одна БД — один EnsureCreated.
|
|
/// </summary>
|
|
public sealed class MatchDbContext : DbContext
|
|
{
|
|
public MatchDbContext(DbContextOptions<MatchDbContext> options)
|
|
: base(options)
|
|
{
|
|
}
|
|
|
|
public DbSet<MatchEntity> Matches => Set<MatchEntity>();
|
|
public DbSet<MatchIntentEntity> Intents => Set<MatchIntentEntity>();
|
|
|
|
public DbSet<UserEntity> Users => Set<UserEntity>();
|
|
public DbSet<RefreshTokenEntity> RefreshTokens => Set<RefreshTokenEntity>();
|
|
public DbSet<CollectionEntity> Collection => Set<CollectionEntity>();
|
|
public DbSet<DeckEntity> Decks => Set<DeckEntity>();
|
|
public DbSet<TransactionEntity> Transactions => Set<TransactionEntity>();
|
|
public DbSet<BoosterOpeningEntity> BoosterOpenings => Set<BoosterOpeningEntity>();
|
|
public DbSet<TrialDeckOwnershipEntity> TrialDeckOwnerships => Set<TrialDeckOwnershipEntity>();
|
|
public DbSet<MatchHistoryEntity> MatchHistory => Set<MatchHistoryEntity>();
|
|
|
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
|
{
|
|
ConfigureMatches(modelBuilder);
|
|
ConfigureMeta(modelBuilder);
|
|
}
|
|
|
|
private static void ConfigureMatches(ModelBuilder modelBuilder)
|
|
{
|
|
modelBuilder.Entity<MatchEntity>(entity =>
|
|
{
|
|
entity.ToTable("matches");
|
|
entity.HasKey(e => e.MatchId);
|
|
entity.Property(e => e.MatchId).HasMaxLength(64);
|
|
entity.Property(e => e.Mode).HasMaxLength(32);
|
|
entity.Property(e => e.Player0Id).HasMaxLength(64);
|
|
entity.Property(e => e.Player1Id).HasMaxLength(64);
|
|
entity.Property(e => e.Deck0Json).HasColumnType("text");
|
|
entity.Property(e => e.Deck1Json).HasColumnType("text");
|
|
entity.Property(e => e.Seed).HasMaxLength(32);
|
|
entity.HasIndex(e => new { e.IsFinished, e.UpdatedAt });
|
|
});
|
|
|
|
modelBuilder.Entity<MatchIntentEntity>(entity =>
|
|
{
|
|
entity.ToTable("match_intents");
|
|
entity.HasKey(e => e.Id);
|
|
entity.Property(e => e.MatchId).HasMaxLength(64);
|
|
entity.Property(e => e.IntentId).HasMaxLength(64);
|
|
entity.Property(e => e.Command).HasColumnType("text");
|
|
entity.Property(e => e.Reason).HasColumnType("text");
|
|
entity.HasIndex(e => e.MatchId);
|
|
});
|
|
}
|
|
|
|
private static void ConfigureMeta(ModelBuilder modelBuilder)
|
|
{
|
|
modelBuilder.Entity<UserEntity>(entity =>
|
|
{
|
|
entity.ToTable("users");
|
|
entity.HasKey(user => user.Id);
|
|
entity.Property(user => user.Id).ValueGeneratedNever();
|
|
entity.Property(user => user.UserName).HasMaxLength(64);
|
|
entity.Property(user => user.NormalizedUserName).HasMaxLength(64);
|
|
entity.Property(user => user.PasswordHash).HasMaxLength(256);
|
|
entity.Property(user => user.SelectedTrialDeckId).HasMaxLength(32);
|
|
entity.HasIndex(user => user.NormalizedUserName).IsUnique();
|
|
});
|
|
|
|
modelBuilder.Entity<RefreshTokenEntity>(entity =>
|
|
{
|
|
entity.ToTable("refresh_tokens");
|
|
entity.HasKey(token => token.Id);
|
|
entity.Property(token => token.Id).ValueGeneratedNever();
|
|
entity.Property(token => token.TokenHash).HasMaxLength(64);
|
|
entity.HasIndex(token => token.TokenHash).IsUnique();
|
|
entity.HasIndex(token => token.UserId);
|
|
});
|
|
|
|
modelBuilder.Entity<CollectionEntity>(entity =>
|
|
{
|
|
entity.ToTable("collection");
|
|
entity.HasKey(card => new { card.UserId, card.PrintingId });
|
|
entity.Property(card => card.PrintingId).HasMaxLength(32);
|
|
entity.HasIndex(card => card.UserId);
|
|
});
|
|
|
|
modelBuilder.Entity<DeckEntity>(entity =>
|
|
{
|
|
entity.ToTable("decks");
|
|
entity.HasKey(deck => deck.Id);
|
|
entity.Property(deck => deck.Id).ValueGeneratedNever();
|
|
entity.Property(deck => deck.Name).HasMaxLength(64);
|
|
entity.Property(deck => deck.Format).HasMaxLength(32);
|
|
entity.Property(deck => deck.CardsJson).HasColumnType("text");
|
|
entity.Property(deck => deck.ShareCode).HasMaxLength(16);
|
|
entity.HasIndex(deck => new { deck.UserId, deck.Name }).IsUnique();
|
|
entity.HasIndex(deck => deck.ShareCode).IsUnique();
|
|
entity.HasIndex(deck => deck.UserId);
|
|
});
|
|
|
|
modelBuilder.Entity<TransactionEntity>(entity =>
|
|
{
|
|
entity.ToTable("transactions");
|
|
entity.HasKey(transaction => transaction.Id);
|
|
entity.Property(transaction => transaction.Id).ValueGeneratedNever();
|
|
entity.Property(transaction => transaction.Currency).HasMaxLength(16);
|
|
entity.Property(transaction => transaction.Reason).HasMaxLength(64);
|
|
entity.Property(transaction => transaction.IdempotencyKey).HasMaxLength(128);
|
|
entity.Property(transaction => transaction.MetadataJson).HasColumnType("text");
|
|
entity.HasIndex(transaction => new { transaction.UserId, transaction.IdempotencyKey }).IsUnique();
|
|
entity.HasIndex(transaction => new { transaction.UserId, transaction.Currency });
|
|
});
|
|
|
|
modelBuilder.Entity<BoosterOpeningEntity>(entity =>
|
|
{
|
|
entity.ToTable("booster_openings");
|
|
entity.HasKey(opening => opening.Id);
|
|
entity.Property(opening => opening.Id).ValueGeneratedNever();
|
|
entity.Property(opening => opening.IdempotencyKey).HasMaxLength(128);
|
|
entity.Property(opening => opening.Payment).HasMaxLength(16);
|
|
entity.Property(opening => opening.CardsJson).HasColumnType("text");
|
|
entity.HasIndex(opening => new { opening.UserId, opening.IdempotencyKey }).IsUnique();
|
|
});
|
|
|
|
modelBuilder.Entity<TrialDeckOwnershipEntity>(entity =>
|
|
{
|
|
entity.ToTable("trial_deck_ownership");
|
|
entity.HasKey(ownership => new { ownership.UserId, ownership.DeckId });
|
|
entity.Property(ownership => ownership.DeckId).HasMaxLength(32);
|
|
});
|
|
|
|
modelBuilder.Entity<MatchHistoryEntity>(entity =>
|
|
{
|
|
entity.ToTable("match_history");
|
|
entity.HasKey(history => history.Id);
|
|
entity.Property(history => history.Id).ValueGeneratedNever();
|
|
entity.Property(history => history.MatchId).HasMaxLength(64);
|
|
entity.Property(history => history.Mode).HasMaxLength(32);
|
|
entity.Property(history => history.OpponentName).HasMaxLength(64);
|
|
entity.Property(history => history.Result).HasMaxLength(16);
|
|
entity.Property(history => history.FinishReason).HasMaxLength(16);
|
|
entity.HasIndex(history => new { history.UserId, history.MatchId }).IsUnique();
|
|
entity.HasIndex(history => new { history.UserId, history.CreatedAt });
|
|
});
|
|
}
|
|
}
|
|
|
|
public sealed class MatchEntity
|
|
{
|
|
public string MatchId { get; set; } = string.Empty;
|
|
public string Mode { get; set; } = string.Empty;
|
|
public string Player0Id { get; set; } = string.Empty;
|
|
public string Player1Id { get; set; } = string.Empty;
|
|
public string Deck0Json { get; set; } = "[]";
|
|
public string Deck1Json { get; set; } = "[]";
|
|
public string Seed { get; set; } = "0";
|
|
|
|
public DateTimeOffset CreatedAt { get; set; }
|
|
public DateTimeOffset UpdatedAt { get; set; }
|
|
|
|
public bool Started { get; set; }
|
|
public long Bank0Ms { get; set; }
|
|
public long Bank1Ms { get; set; }
|
|
public DateTimeOffset? TurnDeadline { get; set; }
|
|
public DateTimeOffset? AbsentSince0 { get; set; }
|
|
public DateTimeOffset? AbsentSince1 { get; set; }
|
|
public DateTimeOffset LastClockSent { get; set; }
|
|
|
|
public bool IsFinished { get; set; }
|
|
public int Winner { get; set; }
|
|
public DateTimeOffset? FinishedAt { get; set; }
|
|
public string FinishReason { get; set; } = "normal";
|
|
public long EventSeq { get; set; }
|
|
}
|
|
|
|
public sealed class MatchIntentEntity
|
|
{
|
|
public long Id { get; set; }
|
|
public string MatchId { get; set; } = string.Empty;
|
|
public string IntentId { get; set; } = string.Empty;
|
|
public int Seat { get; set; }
|
|
public string Command { get; set; } = string.Empty;
|
|
public bool Accepted { get; set; }
|
|
public int Code { get; set; }
|
|
public string? Reason { get; set; }
|
|
public long EventSeq { get; set; }
|
|
}
|