Add cs.money worker stack with per-worker IPRoyal residential proxy
Brings up the pull-model scraper: the .NET C2 hands skin+wear jobs to Python nodriver workers that scrape cs.money and post results back, plus the supporting Core/EFCore data model, migrations, and docker-compose orchestration. IPRoyal proxying lets workers scale horizontally with a distinct residential exit IP each: every worker process mints its own sticky session at startup, and an in-process forwarding proxy injects the gateway auth so Chromium talks only to an auth-free localhost endpoint (zero CDP). On a Cloudflare challenge a worker rotates to a fresh session/IP and re-warms. Verified end-to-end against live IPRoyal: distinct US residential exits per worker and IP rotation on demand. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
using BlueLaminate.EFCore.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace BlueLaminate.EFCore.Configurations;
|
||||
|
||||
public class CsMoneyListingConfiguration : IEntityTypeConfiguration<CsMoneyListing>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<CsMoneyListing> entity)
|
||||
{
|
||||
// cs.money's sell-order id is the natural key; ingest upserts against it and
|
||||
// must never create duplicates.
|
||||
entity.HasIndex(e => e.SellOrderId).IsUnique();
|
||||
|
||||
entity.Property(e => e.Price).HasPrecision(18, 2);
|
||||
entity.Property(e => e.PriceBeforeDiscount).HasPrecision(18, 2);
|
||||
entity.Property(e => e.ComputedPrice).HasPrecision(18, 2);
|
||||
// Full precision to match SkinInstance for exact fingerprint joins.
|
||||
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);
|
||||
entity.HasIndex(e => e.AssetId);
|
||||
|
||||
// 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);
|
||||
|
||||
// Listings roll up to the physical item they represent (shared with CSFloat).
|
||||
entity.HasOne(e => e.SkinInstance)
|
||||
.WithMany()
|
||||
.HasForeignKey(e => e.SkinInstanceId)
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using BlueLaminate.EFCore.Data;
|
||||
using BlueLaminate.EFCore.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace BlueLaminate.EFCore.Configurations;
|
||||
|
||||
public class MarketListingConfiguration : IEntityTypeConfiguration<MarketListing>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<MarketListing> entity)
|
||||
{
|
||||
// Backed by the market_listings SQL view (created in a migration), not a
|
||||
// table — so it's keyless and read-only through EF.
|
||||
entity.HasNoKey();
|
||||
entity.ToView("market_listings", SkinTrackerDbContext.Schema);
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,10 @@ public class SkinConditionConfiguration : IEntityTypeConfiguration<SkinCondition
|
||||
entity.Property(e => e.MinFloat).HasColumnType("numeric(10,9)");
|
||||
entity.Property(e => e.MaxFloat).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);
|
||||
|
||||
entity.HasOne(e => e.Skin)
|
||||
.WithMany(s => s.Conditions)
|
||||
.HasForeignKey(e => e.SkinId);
|
||||
|
||||
Reference in New Issue
Block a user