almost ready

This commit is contained in:
bob
2026-06-01 10:52:06 -05:00
parent 8b0eb0db78
commit 763305ca89
94 changed files with 8766 additions and 2674 deletions

View File

@@ -8,7 +8,8 @@ public class InventoryItemConfiguration : IEntityTypeConfiguration<InventoryItem
{
public void Configure(EntityTypeBuilder<InventoryItem> entity)
{
entity.HasIndex(e => e.AssetId);
// A Steam asset id identifies one physical copy; never store it twice.
entity.HasIndex(e => e.AssetId).IsUnique();
entity.HasOne(e => e.User)
.WithMany(u => u.InventoryItems)

View File

@@ -31,6 +31,14 @@ public class ListingConfiguration : IEntityTypeConfiguration<Listing>
.HasForeignKey(e => e.SkinId)
.OnDelete(DeleteBehavior.SetNull);
// Wear band the sweep targeted (set directly from the sweep unit, not
// best-effort). Set null on delete so a condition row can change without
// blocking its listings — matching the cs.money/skin.land tables.
entity.HasOne(e => e.Condition)
.WithMany()
.HasForeignKey(e => e.ConditionId)
.OnDelete(DeleteBehavior.SetNull);
// Listings roll up to the physical item they represent.
entity.HasOne(e => e.SkinInstance)
.WithMany(i => i.Listings)

View File

@@ -8,12 +8,11 @@ public class SkinConditionConfiguration : IEntityTypeConfiguration<SkinCondition
{
public void Configure(EntityTypeBuilder<SkinCondition> entity)
{
entity.Property(e => e.MinFloat).HasColumnType("numeric(10,9)");
entity.Property(e => e.MaxFloat).HasColumnType("numeric(10,9)");
entity.Property(e => e.FloatMin).HasColumnType("numeric(10,9)");
entity.Property(e => e.FloatMax).HasColumnType("numeric(10,9)");
// The catalogue sweep orders bands by this (never-swept first, then stalest),
// so index it like the equivalent column on skins.
entity.HasIndex(e => e.ListingsSweptAt);
// Per-site "last swept" checkpoints live in skin_condition_sweeps (one row per
// site); see SkinConditionSweepConfiguration for the indexes that order them.
entity.HasOne(e => e.Skin)
.WithMany(s => s.Conditions)

View File

@@ -0,0 +1,24 @@
using BlueLaminate.EFCore.Entities;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace BlueLaminate.EFCore.Configurations;
public class SkinConditionSweepConfiguration : IEntityTypeConfiguration<SkinConditionSweep>
{
public void Configure(EntityTypeBuilder<SkinConditionSweep> entity)
{
// One checkpoint per band per site: the natural key, and what the upsert
// ("stamp") in SweepCheckpoints relies on.
entity.HasIndex(e => new { e.SkinConditionId, e.Source }).IsUnique();
// Each site's sweep orders its bands never-swept-first then stalest; index the
// ordering it scans (filter by source, sort by swept_at).
entity.HasIndex(e => new { e.Source, e.SweptAt });
entity.HasOne(e => e.SkinCondition)
.WithMany(c => c.Sweeps)
.HasForeignKey(e => e.SkinConditionId)
.OnDelete(DeleteBehavior.Cascade);
}
}

View File

@@ -29,9 +29,8 @@ public class SkinConfiguration : IEntityTypeConfiguration<Skin>
.IsUnique()
.HasFilter("def_index IS NOT NULL AND paint_index IS NOT NULL");
// The catalogue sweep orders skins by when they were last swept (nulls
// first) to resume across capped runs; index that ordering.
entity.HasIndex(e => e.ListingsSweptAt);
// Per-site "last swept" checkpoints live in skin_sweeps (one row per site);
// see SkinSweepConfiguration for the indexes that order them.
entity.HasOne(e => e.Weapon)
.WithMany(w => w.Skins)

View File

@@ -0,0 +1,39 @@
using BlueLaminate.EFCore.Entities;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace BlueLaminate.EFCore.Configurations;
public class SkinLandListingConfiguration : IEntityTypeConfiguration<SkinLandListing>
{
public void Configure(EntityTypeBuilder<SkinLandListing> entity)
{
// skin.land's offer id is the natural key; ingest upserts against it and must
// never create duplicates.
entity.HasIndex(e => e.ListingId).IsUnique();
entity.Property(e => e.Price).HasPrecision(18, 2);
// Full precision (matches SkinInstance/cs.money) even though skin.land offers
// aren't fingerprinted — keep the float lossless for later analysis.
entity.Property(e => e.FloatValue).HasColumnType("numeric(20,18)");
// Enum as text so the DB is self-describing (matches the project's leaning).
entity.Property(e => e.Status).HasConversion<string>();
// Targeted scrape: results are filtered/sorted by skin+wear and by activity.
entity.HasIndex(e => new { e.SkinId, e.ConditionId });
entity.HasIndex(e => e.Status);
// Each job targets a known skin, so this link is required (Restrict: a skin with
// live listings shouldn't be deleted out from under them).
entity.HasOne(e => e.Skin)
.WithMany()
.HasForeignKey(e => e.SkinId)
.OnDelete(DeleteBehavior.Restrict);
entity.HasOne(e => e.Condition)
.WithMany()
.HasForeignKey(e => e.ConditionId)
.OnDelete(DeleteBehavior.SetNull);
}
}

View File

@@ -0,0 +1,22 @@
using BlueLaminate.EFCore.Entities;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace BlueLaminate.EFCore.Configurations;
public class SkinSweepConfiguration : IEntityTypeConfiguration<SkinSweep>
{
public void Configure(EntityTypeBuilder<SkinSweep> entity)
{
// One checkpoint per skin per site: the natural key the upsert relies on.
entity.HasIndex(e => new { e.SkinId, e.Source }).IsUnique();
// Mirror SkinConditionSweep: index the (source, swept_at) ordering each sweep scans.
entity.HasIndex(e => new { e.Source, e.SweptAt });
entity.HasOne(e => e.Skin)
.WithMany(s => s.Sweeps)
.HasForeignKey(e => e.SkinId)
.OnDelete(DeleteBehavior.Cascade);
}
}

View File

@@ -8,6 +8,10 @@ public class TradeConfiguration : IEntityTypeConfiguration<Trade>
{
public void Configure(EntityTypeBuilder<Trade> entity)
{
// Steam's trade id is the natural key for an observed trade. Nullable (some
// trades are reconstructed without one); Postgres keeps multiple NULLs distinct.
entity.HasIndex(e => e.SteamTradeId).IsUnique();
entity.HasOne(e => e.FromUser)
.WithMany(u => u.TradesSent)
.HasForeignKey(e => e.FromUserId)