major SnapshotService refactor

This commit is contained in:
Collin M. Barrett 2018-02-03 15:49:32 -06:00
parent 858d0618f7
commit 93da298908
34 changed files with 1023 additions and 405 deletions

View file

@ -0,0 +1,505 @@
// <auto-generated />
using FilterLists.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage;
using System;
namespace FilterLists.Api.Migrations
{
[DbContext(typeof(FilterListsDbContext))]
[Migration("20180203203947_AddSnapshot")]
partial class AddSnapshot
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn)
.HasAnnotation("ProductVersion", "2.0.1-rtm-125");
modelBuilder.Entity("FilterLists.Data.Entities.FilterList", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("SMALLINT UNSIGNED");
b.Property<DateTime>("CreatedDateUtc")
.ValueGeneratedOnAdd()
.HasColumnType("TIMESTAMP");
b.Property<string>("Description")
.HasColumnType("TEXT");
b.Property<string>("DescriptionSourceUrl")
.HasColumnType("TEXT");
b.Property<DateTime?>("DiscontinuedDate");
b.Property<string>("DonateUrl")
.HasColumnType("TEXT");
b.Property<string>("EmailAddress")
.ValueGeneratedOnAdd()
.HasColumnType("VARCHAR(126)")
.HasDefaultValueSql("NULL");
b.Property<string>("ForumUrl")
.HasColumnType("TEXT");
b.Property<string>("HomeUrl")
.HasColumnType("TEXT");
b.Property<string>("IssuesUrl")
.HasColumnType("TEXT");
b.Property<int?>("LicenseId");
b.Property<DateTime>("ModifiedDateUtc")
.ValueGeneratedOnAddOrUpdate()
.HasColumnType("TIMESTAMP");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("VARCHAR(126)");
b.Property<string>("PolicyUrl")
.HasColumnType("TEXT");
b.Property<string>("SubmissionUrl")
.HasColumnType("TEXT");
b.Property<int?>("SyntaxId");
b.Property<string>("ViewUrl")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("LicenseId");
b.HasIndex("SyntaxId");
b.ToTable("filterlists");
});
modelBuilder.Entity("FilterLists.Data.Entities.Junctions.FilterListLanguage", b =>
{
b.Property<int>("FilterListId");
b.Property<int>("LanguageId");
b.Property<DateTime>("CreatedDateUtc")
.ValueGeneratedOnAdd()
.HasColumnType("TIMESTAMP");
b.HasKey("FilterListId", "LanguageId");
b.HasIndex("LanguageId");
b.ToTable("filterlists_languages");
});
modelBuilder.Entity("FilterLists.Data.Entities.Junctions.FilterListMaintainer", b =>
{
b.Property<int>("FilterListId");
b.Property<int>("MaintainerId");
b.Property<DateTime>("CreatedDateUtc")
.ValueGeneratedOnAdd()
.HasColumnType("TIMESTAMP");
b.HasKey("FilterListId", "MaintainerId");
b.HasIndex("MaintainerId");
b.ToTable("filterlists_maintainers");
});
modelBuilder.Entity("FilterLists.Data.Entities.Junctions.Fork", b =>
{
b.Property<int>("ForkFilterListId");
b.Property<int>("UpstreamFilterListId");
b.Property<DateTime>("CreatedDateUtc")
.ValueGeneratedOnAdd()
.HasColumnType("TIMESTAMP");
b.HasKey("ForkFilterListId", "UpstreamFilterListId");
b.HasIndex("UpstreamFilterListId");
b.ToTable("forks");
});
modelBuilder.Entity("FilterLists.Data.Entities.Junctions.Merge", b =>
{
b.Property<int>("MergeFilterListId");
b.Property<int>("UpstreamFilterListId");
b.Property<DateTime>("CreatedDateUtc")
.ValueGeneratedOnAdd()
.HasColumnType("TIMESTAMP");
b.HasKey("MergeFilterListId", "UpstreamFilterListId");
b.HasIndex("UpstreamFilterListId");
b.ToTable("merges");
});
modelBuilder.Entity("FilterLists.Data.Entities.Junctions.SnapshotRule", b =>
{
b.Property<int>("SnapshotId");
b.Property<int>("RuleId");
b.Property<DateTime>("CreatedDateUtc")
.ValueGeneratedOnAdd()
.HasColumnType("TIMESTAMP");
b.HasKey("SnapshotId", "RuleId");
b.HasIndex("RuleId");
b.ToTable("snapshots_rules");
});
modelBuilder.Entity("FilterLists.Data.Entities.Junctions.SoftwareSyntax", b =>
{
b.Property<int>("SoftwareId");
b.Property<int>("SyntaxId");
b.Property<DateTime>("CreatedDateUtc")
.ValueGeneratedOnAdd()
.HasColumnType("TIMESTAMP");
b.HasKey("SoftwareId", "SyntaxId");
b.HasIndex("SyntaxId");
b.ToTable("software_syntaxes");
});
modelBuilder.Entity("FilterLists.Data.Entities.Language", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("SMALLINT UNSIGNED");
b.Property<DateTime>("CreatedDateUtc")
.ValueGeneratedOnAdd()
.HasColumnType("TIMESTAMP");
b.Property<string>("Iso6391")
.ValueGeneratedOnAdd()
.HasColumnType("VARCHAR(2)")
.HasDefaultValueSql("NULL");
b.Property<string>("Iso6392")
.ValueGeneratedOnAdd()
.HasColumnType("VARCHAR(3)")
.HasDefaultValueSql("NULL");
b.Property<string>("Iso6392B")
.ValueGeneratedOnAdd()
.HasColumnType("VARCHAR(3)")
.HasDefaultValueSql("NULL");
b.Property<string>("Iso6392T")
.ValueGeneratedOnAdd()
.HasColumnType("VARCHAR(3)")
.HasDefaultValueSql("NULL");
b.Property<string>("Iso6393")
.ValueGeneratedOnAdd()
.HasColumnType("VARCHAR(3)")
.HasDefaultValueSql("NULL");
b.Property<string>("LocalName")
.ValueGeneratedOnAdd()
.HasColumnType("VARCHAR(126)")
.HasDefaultValueSql("NULL");
b.Property<DateTime>("ModifiedDateUtc")
.ValueGeneratedOnAddOrUpdate()
.HasColumnType("TIMESTAMP");
b.Property<string>("Name")
.ValueGeneratedOnAdd()
.HasColumnType("VARCHAR(126)")
.HasDefaultValueSql("NULL");
b.HasKey("Id");
b.ToTable("languages");
});
modelBuilder.Entity("FilterLists.Data.Entities.License", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("SMALLINT UNSIGNED");
b.Property<DateTime>("CreatedDateUtc")
.ValueGeneratedOnAdd()
.HasColumnType("TIMESTAMP");
b.Property<string>("DescriptionUrl")
.HasColumnType("TEXT");
b.Property<DateTime>("ModifiedDateUtc")
.ValueGeneratedOnAddOrUpdate()
.HasColumnType("TIMESTAMP");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("VARCHAR(126)");
b.Property<bool>("PermissiveAdaptation");
b.Property<bool>("PermissiveCommercial");
b.HasKey("Id");
b.ToTable("licenses");
});
modelBuilder.Entity("FilterLists.Data.Entities.Maintainer", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("SMALLINT UNSIGNED");
b.Property<DateTime>("CreatedDateUtc")
.ValueGeneratedOnAdd()
.HasColumnType("TIMESTAMP");
b.Property<string>("EmailAddress")
.ValueGeneratedOnAdd()
.HasColumnType("VARCHAR(126)")
.HasDefaultValueSql("NULL");
b.Property<string>("HomeUrl")
.HasColumnType("TEXT");
b.Property<DateTime>("ModifiedDateUtc")
.ValueGeneratedOnAddOrUpdate()
.HasColumnType("TIMESTAMP");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("VARCHAR(126)");
b.Property<string>("TwitterHandle")
.ValueGeneratedOnAdd()
.HasColumnType("VARCHAR(126)")
.HasDefaultValueSql("NULL");
b.HasKey("Id");
b.ToTable("maintainers");
});
modelBuilder.Entity("FilterLists.Data.Entities.Rule", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("BIGINT UNSIGNED");
b.Property<DateTime>("CreatedDateUtc")
.ValueGeneratedOnAdd()
.HasColumnType("TIMESTAMP");
b.Property<string>("Raw")
.IsRequired()
.HasColumnType("VARCHAR(8192)");
b.HasKey("Id");
b.ToTable("rules");
});
modelBuilder.Entity("FilterLists.Data.Entities.Snapshot", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("MEDIUMINT UNSIGNED");
b.Property<DateTime>("CreatedDateUtc")
.ValueGeneratedOnAdd()
.HasColumnType("TIMESTAMP");
b.Property<int>("FilterListId");
b.Property<int?>("HttpStatusCode")
.HasColumnType("SMALLINT UNSIGNED");
b.HasKey("Id");
b.HasIndex("FilterListId");
b.ToTable("snapshots");
});
modelBuilder.Entity("FilterLists.Data.Entities.Software", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("SMALLINT UNSIGNED");
b.Property<DateTime>("CreatedDateUtc")
.ValueGeneratedOnAdd()
.HasColumnType("TIMESTAMP");
b.Property<string>("DownloadUrl")
.HasColumnType("TEXT");
b.Property<string>("HomeUrl")
.HasColumnType("TEXT");
b.Property<DateTime>("ModifiedDateUtc")
.ValueGeneratedOnAddOrUpdate()
.HasColumnType("TIMESTAMP");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("VARCHAR(126)");
b.HasKey("Id");
b.ToTable("software");
});
modelBuilder.Entity("FilterLists.Data.Entities.Syntax", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("SMALLINT UNSIGNED");
b.Property<DateTime>("CreatedDateUtc")
.ValueGeneratedOnAdd()
.HasColumnType("TIMESTAMP");
b.Property<string>("DefinitionUrl")
.HasColumnType("TEXT");
b.Property<DateTime>("ModifiedDateUtc")
.ValueGeneratedOnAddOrUpdate()
.HasColumnType("TIMESTAMP");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("VARCHAR(126)");
b.HasKey("Id");
b.ToTable("syntaxes");
});
modelBuilder.Entity("FilterLists.Data.Entities.FilterList", b =>
{
b.HasOne("FilterLists.Data.Entities.License", "License")
.WithMany("FilterLists")
.HasForeignKey("LicenseId");
b.HasOne("FilterLists.Data.Entities.Syntax", "Syntax")
.WithMany("FilterLists")
.HasForeignKey("SyntaxId");
});
modelBuilder.Entity("FilterLists.Data.Entities.Junctions.FilterListLanguage", b =>
{
b.HasOne("FilterLists.Data.Entities.FilterList", "FilterList")
.WithMany("FilterListLanguages")
.HasForeignKey("FilterListId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("FilterLists.Data.Entities.Language", "Language")
.WithMany("FilterListLanguages")
.HasForeignKey("LanguageId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("FilterLists.Data.Entities.Junctions.FilterListMaintainer", b =>
{
b.HasOne("FilterLists.Data.Entities.FilterList", "FilterList")
.WithMany("FilterListMaintainers")
.HasForeignKey("FilterListId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("FilterLists.Data.Entities.Maintainer", "Maintainer")
.WithMany("FilterListMaintainers")
.HasForeignKey("MaintainerId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("FilterLists.Data.Entities.Junctions.Fork", b =>
{
b.HasOne("FilterLists.Data.Entities.FilterList", "ForkFilterList")
.WithMany("ForkFilterLists")
.HasForeignKey("ForkFilterListId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("FilterLists.Data.Entities.FilterList", "UpstreamFilterList")
.WithMany("UpstreamForkFilterLists")
.HasForeignKey("UpstreamFilterListId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("FilterLists.Data.Entities.Junctions.Merge", b =>
{
b.HasOne("FilterLists.Data.Entities.FilterList", "MergeFilterList")
.WithMany("MergeFilterLists")
.HasForeignKey("MergeFilterListId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("FilterLists.Data.Entities.FilterList", "UpstreamFilterList")
.WithMany("UpstreamMergeFilterLists")
.HasForeignKey("UpstreamFilterListId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("FilterLists.Data.Entities.Junctions.SnapshotRule", b =>
{
b.HasOne("FilterLists.Data.Entities.Rule", "Rule")
.WithMany("SnapshotRules")
.HasForeignKey("RuleId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("FilterLists.Data.Entities.Snapshot", "Snapshot")
.WithMany("SnapshotRules")
.HasForeignKey("SnapshotId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("FilterLists.Data.Entities.Junctions.SoftwareSyntax", b =>
{
b.HasOne("FilterLists.Data.Entities.Software", "Software")
.WithMany("SoftwareSyntaxes")
.HasForeignKey("SoftwareId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("FilterLists.Data.Entities.Syntax", "Syntax")
.WithMany("SoftwareSyntaxes")
.HasForeignKey("SyntaxId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("FilterLists.Data.Entities.Snapshot", b =>
{
b.HasOne("FilterLists.Data.Entities.FilterList", "FilterList")
.WithMany("Snapshots")
.HasForeignKey("FilterListId")
.OnDelete(DeleteBehavior.Cascade);
});
#pragma warning restore 612, 618
}
}
}

View file

@ -0,0 +1,143 @@
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using System;
using System.Collections.Generic;
namespace FilterLists.Api.Migrations
{
public partial class AddSnapshot : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "filterlists_rules");
migrationBuilder.DropTable(
name: "scrapes");
migrationBuilder.CreateTable(
name: "snapshots",
columns: table => new
{
Id = table.Column<int>(type: "MEDIUMINT UNSIGNED", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
CreatedDateUtc = table.Column<DateTime>(type: "TIMESTAMP", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
FilterListId = table.Column<int>(nullable: false),
HttpStatusCode = table.Column<int>(type: "SMALLINT UNSIGNED", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_snapshots", x => x.Id);
table.ForeignKey(
name: "FK_snapshots_filterlists_FilterListId",
column: x => x.FilterListId,
principalTable: "filterlists",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "snapshots_rules",
columns: table => new
{
SnapshotId = table.Column<int>(nullable: false),
RuleId = table.Column<int>(nullable: false),
CreatedDateUtc = table.Column<DateTime>(type: "TIMESTAMP", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn)
},
constraints: table =>
{
table.PrimaryKey("PK_snapshots_rules", x => new { x.SnapshotId, x.RuleId });
table.ForeignKey(
name: "FK_snapshots_rules_rules_RuleId",
column: x => x.RuleId,
principalTable: "rules",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_snapshots_rules_snapshots_SnapshotId",
column: x => x.SnapshotId,
principalTable: "snapshots",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_snapshots_FilterListId",
table: "snapshots",
column: "FilterListId");
migrationBuilder.CreateIndex(
name: "IX_snapshots_rules_RuleId",
table: "snapshots_rules",
column: "RuleId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "snapshots_rules");
migrationBuilder.DropTable(
name: "snapshots");
migrationBuilder.CreateTable(
name: "filterlists_rules",
columns: table => new
{
FilterListId = table.Column<int>(nullable: false),
RuleId = table.Column<int>(nullable: false),
CreatedDateUtc = table.Column<DateTime>(type: "TIMESTAMP", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn)
},
constraints: table =>
{
table.PrimaryKey("PK_filterlists_rules", x => new { x.FilterListId, x.RuleId });
table.ForeignKey(
name: "FK_filterlists_rules_filterlists_FilterListId",
column: x => x.FilterListId,
principalTable: "filterlists",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_filterlists_rules_rules_RuleId",
column: x => x.RuleId,
principalTable: "rules",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "scrapes",
columns: table => new
{
Id = table.Column<int>(type: "SMALLINT UNSIGNED", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
CreatedDateUtc = table.Column<DateTime>(type: "TIMESTAMP", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
FilterListId = table.Column<int>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_scrapes", x => x.Id);
table.ForeignKey(
name: "FK_scrapes_filterlists_FilterListId",
column: x => x.FilterListId,
principalTable: "filterlists",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_filterlists_rules_RuleId",
table: "filterlists_rules",
column: "RuleId");
migrationBuilder.CreateIndex(
name: "IX_scrapes_FilterListId",
table: "scrapes",
column: "FilterListId");
}
}
}

View file

@ -118,23 +118,6 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.ToTable("filterlists_maintainers");
});
modelBuilder.Entity("FilterLists.Data.Entities.Junctions.FilterListRule", b =>
{
b.Property<int>("FilterListId");
b.Property<int>("RuleId");
b.Property<DateTime>("CreatedDateUtc")
.ValueGeneratedOnAdd()
.HasColumnType("TIMESTAMP");
b.HasKey("FilterListId", "RuleId");
b.HasIndex("RuleId");
b.ToTable("filterlists_rules");
});
modelBuilder.Entity("FilterLists.Data.Entities.Junctions.Fork", b =>
{
b.Property<int>("ForkFilterListId");
@ -169,6 +152,23 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.ToTable("merges");
});
modelBuilder.Entity("FilterLists.Data.Entities.Junctions.SnapshotRule", b =>
{
b.Property<int>("SnapshotId");
b.Property<int>("RuleId");
b.Property<DateTime>("CreatedDateUtc")
.ValueGeneratedOnAdd()
.HasColumnType("TIMESTAMP");
b.HasKey("SnapshotId", "RuleId");
b.HasIndex("RuleId");
b.ToTable("snapshots_rules");
});
modelBuilder.Entity("FilterLists.Data.Entities.Junctions.SoftwareSyntax", b =>
{
b.Property<int>("SoftwareId");
@ -325,11 +325,11 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.ToTable("rules");
});
modelBuilder.Entity("FilterLists.Data.Entities.Scrape", b =>
modelBuilder.Entity("FilterLists.Data.Entities.Snapshot", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("SMALLINT UNSIGNED");
.HasColumnType("MEDIUMINT UNSIGNED");
b.Property<DateTime>("CreatedDateUtc")
.ValueGeneratedOnAdd()
@ -337,11 +337,14 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.Property<int>("FilterListId");
b.Property<int?>("HttpStatusCode")
.HasColumnType("SMALLINT UNSIGNED");
b.HasKey("Id");
b.HasIndex("FilterListId");
b.ToTable("scrapes");
b.ToTable("snapshots");
});
modelBuilder.Entity("FilterLists.Data.Entities.Software", b =>
@ -401,11 +404,11 @@ protected override void BuildModel(ModelBuilder modelBuilder)
modelBuilder.Entity("FilterLists.Data.Entities.FilterList", b =>
{
b.HasOne("FilterLists.Data.Entities.License")
b.HasOne("FilterLists.Data.Entities.License", "License")
.WithMany("FilterLists")
.HasForeignKey("LicenseId");
b.HasOne("FilterLists.Data.Entities.Syntax")
b.HasOne("FilterLists.Data.Entities.Syntax", "Syntax")
.WithMany("FilterLists")
.HasForeignKey("SyntaxId");
});
@ -436,19 +439,6 @@ protected override void BuildModel(ModelBuilder modelBuilder)
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("FilterLists.Data.Entities.Junctions.FilterListRule", b =>
{
b.HasOne("FilterLists.Data.Entities.FilterList", "FilterList")
.WithMany("FilterListRules")
.HasForeignKey("FilterListId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("FilterLists.Data.Entities.Rule", "Rule")
.WithMany("FilterListRules")
.HasForeignKey("RuleId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("FilterLists.Data.Entities.Junctions.Fork", b =>
{
b.HasOne("FilterLists.Data.Entities.FilterList", "ForkFilterList")
@ -475,6 +465,19 @@ protected override void BuildModel(ModelBuilder modelBuilder)
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("FilterLists.Data.Entities.Junctions.SnapshotRule", b =>
{
b.HasOne("FilterLists.Data.Entities.Rule", "Rule")
.WithMany("SnapshotRules")
.HasForeignKey("RuleId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("FilterLists.Data.Entities.Snapshot", "Snapshot")
.WithMany("SnapshotRules")
.HasForeignKey("SnapshotId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("FilterLists.Data.Entities.Junctions.SoftwareSyntax", b =>
{
b.HasOne("FilterLists.Data.Entities.Software", "Software")
@ -488,10 +491,10 @@ protected override void BuildModel(ModelBuilder modelBuilder)
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("FilterLists.Data.Entities.Scrape", b =>
modelBuilder.Entity("FilterLists.Data.Entities.Snapshot", b =>
{
b.HasOne("FilterLists.Data.Entities.FilterList")
.WithMany("Scrapes")
b.HasOne("FilterLists.Data.Entities.FilterList", "FilterList")
.WithMany("Snapshots")
.HasForeignKey("FilterListId")
.OnDelete(DeleteBehavior.Cascade);
});

View file

@ -1,27 +0,0 @@
using System.Threading.Tasks;
using FilterLists.Services.ScrapeService;
using Microsoft.AspNetCore.Mvc;
namespace FilterLists.Api.V1.Controllers
{
public class ScrapeController : BaseController
{
private readonly ScrapeService scrapeService;
public ScrapeController(ScrapeService scrapeService)
{
this.scrapeService = scrapeService;
}
[HttpPost]
public async Task<IActionResult> Index()
{
#if DEBUG
await scrapeService.ScrapeAsync(3);
return Ok();
#else
return StatusCode(403);
#endif
}
}
}

View file

@ -0,0 +1,27 @@
using System.Threading.Tasks;
using FilterLists.Services.SnapshotService;
using Microsoft.AspNetCore.Mvc;
namespace FilterLists.Api.V1.Controllers
{
public class SnapshotController : BaseController
{
private readonly SnapshotService snapshotService;
public SnapshotController(SnapshotService snapshotService)
{
this.snapshotService = snapshotService;
}
[HttpPost]
public async Task<IActionResult> Index()
{
#if DEBUG
await snapshotService.CaptureSnapshotsAsync(1);
return Ok();
#else
return StatusCode(403);
#endif
}
}
}

View file

@ -24,8 +24,7 @@ public class FilterList : BaseEntity
public ICollection<Merge> UpstreamMergeFilterLists { get; set; }
public string Name { get; set; }
public string PolicyUrl { get; set; }
public ICollection<FilterListRule> FilterListRules { get; set; }
public ICollection<Scrape> Scrapes { get; set; }
public ICollection<Snapshot> Snapshots { get; set; }
public string SubmissionUrl { get; set; }
public int? SyntaxId { get; set; }
public Syntax Syntax { get; set; }

View file

@ -1,9 +1,9 @@
namespace FilterLists.Data.Entities.Junctions
{
public class FilterListRule : BaseJunction
public class SnapshotRule : BaseJunction
{
public int FilterListId { get; set; }
public FilterList FilterList { get; set; }
public int SnapshotId { get; set; }
public Snapshot Snapshot { get; set; }
public int RuleId { get; set; }
public Rule Rule { get; set; }

View file

@ -5,7 +5,7 @@ namespace FilterLists.Data.Entities
{
public class Rule : BaseEntity
{
public ICollection<FilterListRule> FilterListRules { get; set; }
public string Raw { get; set; }
public ICollection<SnapshotRule> SnapshotRules { get; set; }
}
}

View file

@ -1,8 +0,0 @@
namespace FilterLists.Data.Entities
{
public class Scrape : BaseEntity
{
public int FilterListId { get; set; }
public FilterList FilterList { get; set; }
}
}

View file

@ -0,0 +1,13 @@
using System.Collections.Generic;
using FilterLists.Data.Entities.Junctions;
namespace FilterLists.Data.Entities
{
public class Snapshot : BaseEntity
{
public int FilterListId { get; set; }
public FilterList FilterList { get; set; }
public int? HttpStatusCode { get; set; }
public ICollection<SnapshotRule> SnapshotRules { get; set; }
}
}

View file

@ -9,13 +9,13 @@ public class BaseEntityTypeConfiguration<TEntity> : IEntityTypeConfiguration<TEn
public virtual void Configure(EntityTypeBuilder<TEntity> entityTypeBuilder)
{
entityTypeBuilder.Property(x => x.Id)
.HasColumnType("SMALLINT UNSIGNED");
.HasColumnType("SMALLINT UNSIGNED");
entityTypeBuilder.Property(x => x.CreatedDateUtc)
.HasColumnType("TIMESTAMP")
.ValueGeneratedOnAdd();
.HasColumnType("TIMESTAMP")
.ValueGeneratedOnAdd();
entityTypeBuilder.Property(x => x.ModifiedDateUtc)
.HasColumnType("TIMESTAMP")
.ValueGeneratedOnAddOrUpdate();
.HasColumnType("TIMESTAMP")
.ValueGeneratedOnAddOrUpdate();
}
}
}

View file

@ -9,33 +9,31 @@ public class FilterListTypeConfiguration : BaseEntityTypeConfiguration<FilterLis
public override void Configure(EntityTypeBuilder<FilterList> entityTypeBuilder)
{
base.Configure(entityTypeBuilder);
entityTypeBuilder.ToTable("filterlists");
entityTypeBuilder.Property(x => x.Description)
.HasColumnType("TEXT");
.HasColumnType("TEXT");
entityTypeBuilder.Property(x => x.DescriptionSourceUrl)
.HasColumnType("TEXT");
.HasColumnType("TEXT");
entityTypeBuilder.Property(x => x.DonateUrl)
.HasColumnType("TEXT");
.HasColumnType("TEXT");
entityTypeBuilder.Property(x => x.EmailAddress)
.HasColumnType("VARCHAR(126)")
.HasDefaultValueSql("NULL");
.HasColumnType("VARCHAR(126)")
.HasDefaultValueSql("NULL");
entityTypeBuilder.Property(x => x.ForumUrl)
.HasColumnType("TEXT");
.HasColumnType("TEXT");
entityTypeBuilder.Property(x => x.HomeUrl)
.HasColumnType("TEXT");
.HasColumnType("TEXT");
entityTypeBuilder.Property(x => x.IssuesUrl)
.HasColumnType("TEXT");
.HasColumnType("TEXT");
entityTypeBuilder.Property(x => x.Name)
.HasColumnType("VARCHAR(126)")
.IsRequired();
.HasColumnType("VARCHAR(126)")
.IsRequired();
entityTypeBuilder.Property(x => x.PolicyUrl)
.HasColumnType("TEXT");
.HasColumnType("TEXT");
entityTypeBuilder.Property(x => x.SubmissionUrl)
.HasColumnType("TEXT");
.HasColumnType("TEXT");
entityTypeBuilder.Property(x => x.ViewUrl)
.HasColumnType("TEXT");
.HasColumnType("TEXT");
}
}
}

View file

@ -10,8 +10,8 @@ public class BaseJunctionTypeConfiguration<TJunction> : IEntityTypeConfiguration
public virtual void Configure(EntityTypeBuilder<TJunction> entityTypeBuilder)
{
entityTypeBuilder.Property(x => x.CreatedDateUtc)
.HasColumnType("TIMESTAMP")
.ValueGeneratedOnAdd();
.HasColumnType("TIMESTAMP")
.ValueGeneratedOnAdd();
}
}
}

View file

@ -1,16 +0,0 @@
using FilterLists.Data.Entities.Junctions;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace FilterLists.Data.EntityTypeConfigurations.Junctions
{
public class FilterListRuleTypeConfiguration : BaseJunctionTypeConfiguration<FilterListRule>
{
public override void Configure(EntityTypeBuilder<FilterListRule> entityTypeBuilder)
{
base.Configure(entityTypeBuilder);
entityTypeBuilder.ToTable("filterlists_rules");
entityTypeBuilder.HasKey(x => new {x.FilterListId, x.RuleId});
}
}
}

View file

@ -11,6 +11,12 @@ public override void Configure(EntityTypeBuilder<Fork> entityTypeBuilder)
base.Configure(entityTypeBuilder);
entityTypeBuilder.ToTable("forks");
entityTypeBuilder.HasKey(x => new {x.ForkFilterListId, x.UpstreamFilterListId});
entityTypeBuilder.HasOne(x => x.ForkFilterList)
.WithMany(x => x.ForkFilterLists)
.HasForeignKey(x => x.ForkFilterListId);
entityTypeBuilder.HasOne(x => x.UpstreamFilterList)
.WithMany(x => x.UpstreamForkFilterLists)
.HasForeignKey(x => x.UpstreamFilterListId);
}
}
}

View file

@ -11,6 +11,12 @@ public override void Configure(EntityTypeBuilder<Merge> entityTypeBuilder)
base.Configure(entityTypeBuilder);
entityTypeBuilder.ToTable("merges");
entityTypeBuilder.HasKey(x => new {x.MergeFilterListId, x.UpstreamFilterListId});
entityTypeBuilder.HasOne(x => x.MergeFilterList)
.WithMany(x => x.MergeFilterLists)
.HasForeignKey(x => x.MergeFilterListId);
entityTypeBuilder.HasOne(x => x.UpstreamFilterList)
.WithMany(x => x.UpstreamMergeFilterLists)
.HasForeignKey(x => x.UpstreamFilterListId);
}
}
}

View file

@ -0,0 +1,16 @@
using FilterLists.Data.Entities.Junctions;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace FilterLists.Data.EntityTypeConfigurations.Junctions
{
public class SnapshotRuleTypeConfiguration : BaseJunctionTypeConfiguration<SnapshotRule>
{
public override void Configure(EntityTypeBuilder<SnapshotRule> entityTypeBuilder)
{
base.Configure(entityTypeBuilder);
entityTypeBuilder.ToTable("snapshots_rules");
entityTypeBuilder.HasKey(x => new {x.SnapshotId, x.RuleId});
}
}
}

View file

@ -9,30 +9,28 @@ public class LanguageTypeConfiguration : BaseEntityTypeConfiguration<Language>
public override void Configure(EntityTypeBuilder<Language> entityTypeBuilder)
{
base.Configure(entityTypeBuilder);
entityTypeBuilder.ToTable("languages");
entityTypeBuilder.Property(x => x.Iso6391)
.HasColumnType("VARCHAR(2)")
.HasDefaultValueSql("NULL");
.HasColumnType("VARCHAR(2)")
.HasDefaultValueSql("NULL");
entityTypeBuilder.Property(x => x.Iso6392)
.HasColumnType("VARCHAR(3)")
.HasDefaultValueSql("NULL");
.HasColumnType("VARCHAR(3)")
.HasDefaultValueSql("NULL");
entityTypeBuilder.Property(x => x.Iso6392B)
.HasColumnType("VARCHAR(3)")
.HasDefaultValueSql("NULL");
.HasColumnType("VARCHAR(3)")
.HasDefaultValueSql("NULL");
entityTypeBuilder.Property(x => x.Iso6392T)
.HasColumnType("VARCHAR(3)")
.HasDefaultValueSql("NULL");
.HasColumnType("VARCHAR(3)")
.HasDefaultValueSql("NULL");
entityTypeBuilder.Property(x => x.Iso6393)
.HasColumnType("VARCHAR(3)")
.HasDefaultValueSql("NULL");
.HasColumnType("VARCHAR(3)")
.HasDefaultValueSql("NULL");
entityTypeBuilder.Property(x => x.LocalName)
.HasColumnType("VARCHAR(126)")
.HasDefaultValueSql("NULL");
.HasColumnType("VARCHAR(126)")
.HasDefaultValueSql("NULL");
entityTypeBuilder.Property(x => x.Name)
.HasColumnType("VARCHAR(126)")
.HasDefaultValueSql("NULL");
.HasColumnType("VARCHAR(126)")
.HasDefaultValueSql("NULL");
}
}
}

View file

@ -9,14 +9,12 @@ public class LicenseTypeConfiguration : BaseEntityTypeConfiguration<License>
public override void Configure(EntityTypeBuilder<License> entityTypeBuilder)
{
base.Configure(entityTypeBuilder);
entityTypeBuilder.ToTable("licenses");
entityTypeBuilder.Property(x => x.DescriptionUrl)
.HasColumnType("TEXT");
.HasColumnType("TEXT");
entityTypeBuilder.Property(x => x.Name)
.HasColumnType("VARCHAR(126)")
.IsRequired();
.HasColumnType("VARCHAR(126)")
.IsRequired();
}
}
}

View file

@ -9,20 +9,18 @@ public class MaintainerTypeConfiguration : BaseEntityTypeConfiguration<Maintaine
public override void Configure(EntityTypeBuilder<Maintainer> entityTypeBuilder)
{
base.Configure(entityTypeBuilder);
entityTypeBuilder.ToTable("maintainers");
entityTypeBuilder.Property(x => x.EmailAddress)
.HasColumnType("VARCHAR(126)")
.HasDefaultValueSql("NULL");
.HasColumnType("VARCHAR(126)")
.HasDefaultValueSql("NULL");
entityTypeBuilder.Property(x => x.HomeUrl)
.HasColumnType("TEXT");
.HasColumnType("TEXT");
entityTypeBuilder.Property(x => x.Name)
.HasColumnType("VARCHAR(126)")
.IsRequired();
.HasColumnType("VARCHAR(126)")
.IsRequired();
entityTypeBuilder.Property(x => x.TwitterHandle)
.HasColumnType("VARCHAR(126)")
.HasDefaultValueSql("NULL"); ;
.HasColumnType("VARCHAR(126)")
.HasDefaultValueSql("NULL");
}
}
}

View file

@ -9,17 +9,13 @@ public class RuleTypeConfiguration : BaseEntityTypeConfiguration<Rule>
public override void Configure(EntityTypeBuilder<Rule> entityTypeBuilder)
{
base.Configure(entityTypeBuilder);
entityTypeBuilder.ToTable("rules");
entityTypeBuilder.Property(x => x.Id)
.HasColumnType("BIGINT UNSIGNED");
.HasColumnType("BIGINT UNSIGNED");
entityTypeBuilder.Ignore(x => x.ModifiedDateUtc);
entityTypeBuilder.Property(x => x.Raw)
.HasColumnType("VARCHAR(8192)")
.IsRequired();
.HasColumnType("VARCHAR(8192)")
.IsRequired();
}
}
}

View file

@ -1,18 +0,0 @@
using FilterLists.Data.Entities;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace FilterLists.Data.EntityTypeConfigurations
{
public class ScrapeTypeConfiguration : BaseEntityTypeConfiguration<Scrape>
{
public override void Configure(EntityTypeBuilder<Scrape> entityTypeBuilder)
{
base.Configure(entityTypeBuilder);
entityTypeBuilder.ToTable("scrapes");
entityTypeBuilder.Ignore(x => x.ModifiedDateUtc);
}
}
}

View file

@ -0,0 +1,20 @@
using FilterLists.Data.Entities;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace FilterLists.Data.EntityTypeConfigurations
{
public class SnapshotTypeConfiguration : BaseEntityTypeConfiguration<Snapshot>
{
public override void Configure(EntityTypeBuilder<Snapshot> entityTypeBuilder)
{
base.Configure(entityTypeBuilder);
entityTypeBuilder.ToTable("snapshots");
entityTypeBuilder.Property(x => x.Id)
.HasColumnType("MEDIUMINT UNSIGNED");
entityTypeBuilder.Property(x => x.HttpStatusCode)
.HasColumnType("SMALLINT UNSIGNED");
entityTypeBuilder.Ignore(x => x.ModifiedDateUtc);
}
}
}

View file

@ -9,16 +9,14 @@ public class SoftwareTypeConfiguration : BaseEntityTypeConfiguration<Software>
public override void Configure(EntityTypeBuilder<Software> entityTypeBuilder)
{
base.Configure(entityTypeBuilder);
entityTypeBuilder.ToTable("software");
entityTypeBuilder.Property(x => x.DownloadUrl)
.HasColumnType("TEXT");
.HasColumnType("TEXT");
entityTypeBuilder.Property(x => x.HomeUrl)
.HasColumnType("TEXT");
.HasColumnType("TEXT");
entityTypeBuilder.Property(x => x.Name)
.HasColumnType("VARCHAR(126)")
.IsRequired();
.HasColumnType("VARCHAR(126)")
.IsRequired();
}
}
}

View file

@ -9,14 +9,12 @@ public class SyntaxTypeConfiguration : BaseEntityTypeConfiguration<Syntax>
public override void Configure(EntityTypeBuilder<Syntax> entityTypeBuilder)
{
base.Configure(entityTypeBuilder);
entityTypeBuilder.ToTable("syntaxes");
entityTypeBuilder.Property(x => x.DefinitionUrl)
.HasColumnType("TEXT");
.HasColumnType("TEXT");
entityTypeBuilder.Property(x => x.Name)
.HasColumnType("VARCHAR(126)")
.IsRequired();
.HasColumnType("VARCHAR(126)")
.IsRequired();
}
}
}

View file

@ -8,18 +8,19 @@ namespace FilterLists.Data
{
public class FilterListsDbContext : DbContext
{
public FilterListsDbContext(DbContextOptions options)
: base(options)
public FilterListsDbContext(DbContextOptions options) : base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
ApplyConfigurationsEntities(modelBuilder);
ApplyConfigurationsJunctions(modelBuilder);
base.OnModelCreating(modelBuilder);
}
#region Entities
private static void ApplyConfigurationsEntities(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration(new FilterListTypeConfiguration());
@ -27,55 +28,17 @@ private static void ApplyConfigurationsEntities(ModelBuilder modelBuilder)
modelBuilder.ApplyConfiguration(new LicenseTypeConfiguration());
modelBuilder.ApplyConfiguration(new MaintainerTypeConfiguration());
modelBuilder.ApplyConfiguration(new RuleTypeConfiguration());
modelBuilder.ApplyConfiguration(new ScrapeTypeConfiguration());
modelBuilder.ApplyConfiguration(new SnapshotTypeConfiguration());
modelBuilder.ApplyConfiguration(new SoftwareTypeConfiguration());
modelBuilder.ApplyConfiguration(new SyntaxTypeConfiguration());
}
private static void ApplyConfigurationsJunctions(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration(new FilterListLanguageTypeConfiguration());
modelBuilder.ApplyConfiguration(new FilterListMaintainerTypeConfiguration());
modelBuilder.ApplyConfiguration(new FilterListRuleTypeConfiguration());
modelBuilder.ApplyConfiguration(new ForkTypeConfiguration());
ConfigureForkSelfReferencing(modelBuilder);
modelBuilder.ApplyConfiguration(new MergeTypeConfiguration());
ConfigureMergeSelfReferencing(modelBuilder);
modelBuilder.ApplyConfiguration(new SoftwareSyntaxTypeConfiguration());
}
private static void ConfigureForkSelfReferencing(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Fork>()
.HasOne(x => x.ForkFilterList)
.WithMany(x => x.ForkFilterLists)
.HasForeignKey(x => x.ForkFilterListId);
modelBuilder.Entity<Fork>()
.HasOne(x => x.UpstreamFilterList)
.WithMany(x => x.UpstreamForkFilterLists)
.HasForeignKey(x => x.UpstreamFilterListId);
}
private static void ConfigureMergeSelfReferencing(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Merge>()
.HasOne(x => x.MergeFilterList)
.WithMany(x => x.MergeFilterLists)
.HasForeignKey(x => x.MergeFilterListId);
modelBuilder.Entity<Merge>()
.HasOne(x => x.UpstreamFilterList)
.WithMany(x => x.UpstreamMergeFilterLists)
.HasForeignKey(x => x.UpstreamFilterListId);
}
#region Entities
public DbSet<FilterList> FilterLists { get; set; }
public DbSet<Language> Languages { get; set; }
public DbSet<License> Licenses { get; set; }
public DbSet<Maintainer> Maintainers { get; set; }
public DbSet<Rule> Rules { get; set; }
public DbSet<Scrape> Scrapes { get; set; }
public DbSet<Snapshot> Snapshots { get; set; }
public DbSet<Software> Software { get; set; }
public DbSet<Syntax> Syntaxes { get; set; }
@ -83,11 +46,21 @@ private static void ConfigureMergeSelfReferencing(ModelBuilder modelBuilder)
#region Junctions
private static void ApplyConfigurationsJunctions(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration(new FilterListLanguageTypeConfiguration());
modelBuilder.ApplyConfiguration(new FilterListMaintainerTypeConfiguration());
modelBuilder.ApplyConfiguration(new ForkTypeConfiguration());
modelBuilder.ApplyConfiguration(new MergeTypeConfiguration());
modelBuilder.ApplyConfiguration(new SnapshotRuleTypeConfiguration());
modelBuilder.ApplyConfiguration(new SoftwareSyntaxTypeConfiguration());
}
public DbSet<FilterListLanguage> FilterListLanguages { get; set; }
public DbSet<FilterListMaintainer> FilterListMaintainers { get; set; }
public DbSet<FilterListRule> FilterListRules { get; set; }
public DbSet<Fork> Forks { get; set; }
public DbSet<Merge> Merges { get; set; }
public DbSet<SnapshotRule> SnapshotRules { get; set; }
public DbSet<SoftwareSyntax> SoftwareSyntaxes { get; set; }
#endregion

View file

@ -75,7 +75,6 @@ private static List<TEntityType> GetSeedRows<TEntityType>(string dataPath)
private static string CreateRowValues<TEntityType>(IEnumerable<IProperty> properties, TEntityType row)
{
//TODO: use EF nav entity rather than foreign key so foreign key doesn't need to be explicitly in model (ie, License rather than LicenseId)
return (from property in properties
let value = row.GetType().GetProperty(property.Name).GetValue(row)
select FormatDataForMySql(property, value)).Aggregate("",

View file

@ -17,7 +17,7 @@ public static void AddFilterListsServices(this IServiceCollection services, ICon
b => b.MigrationsAssembly("FilterLists.Api")));
services.TryAddScoped<FilterListService.FilterListService>();
services.TryAddScoped<SeedService.SeedService>();
services.TryAddScoped<ScrapeService.ScrapeService>();
services.TryAddScoped<SnapshotService.SnapshotService>();
services.AddAutoMapper();
}
}

View file

@ -1,105 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using AutoMapper.QueryableExtensions;
using FilterLists.Data;
using Microsoft.EntityFrameworkCore;
namespace FilterLists.Services.ScrapeService
{
public class ScrapeService
{
private readonly FilterListsDbContext dbContext;
public ScrapeService(FilterListsDbContext dbContext)
{
this.dbContext = dbContext;
}
//TODO: call via scheduled job
public async Task ScrapeAsync(int batchSize)
{
var lists = await GetNextListsToScrape(batchSize);
var snapshots = await GetSnapshots(lists);
await SaveSnapshots(snapshots);
}
private async Task<List<FilterListViewUrlDto>> GetNextListsToScrape(int batchSize)
{
var neverScraped = await GetNeverScrapedLists(batchSize);
if (neverScraped.Count == batchSize)
return neverScraped;
var leastRecentlyScraped = await GetLeastRecentlyScrapedLists(batchSize - neverScraped.Count);
return neverScraped.Concat(leastRecentlyScraped).ToList();
}
private async Task<List<FilterListViewUrlDto>> GetNeverScrapedLists(int batchSize)
{
return await dbContext.FilterLists
.Where(x => x.Scrapes.Count == 0)
.Take(batchSize)
.ProjectTo<FilterListViewUrlDto>()
.ToListAsync();
}
private async Task<List<FilterListViewUrlDto>> GetLeastRecentlyScrapedLists(int batchSize)
{
return await dbContext.Scrapes
.GroupBy(x => x.FilterList)
.Select(x => x.OrderByDescending(y => y.CreatedDateUtc).First())
.OrderBy(x => x.CreatedDateUtc)
.Select(x => x.FilterList)
.Take(batchSize)
.ProjectTo<FilterListViewUrlDto>()
.ToListAsync();
}
private async Task<IEnumerable<Snapshot>> GetSnapshots(IEnumerable<FilterListViewUrlDto> lists)
{
return await Task.WhenAll(lists
.Select(async list => new Snapshot(dbContext, list.Id, await TryGetContent(list.ViewUrl)))
.Where(x => x.Result.HasRules));
}
private static async Task<string> TryGetContent(string url)
{
try
{
return await GetHttpResponseMessageContent(url);
}
catch (Exception)
{
//TODO: log exception
return null;
}
}
private static async Task<string> GetHttpResponseMessageContent(string url)
{
using (var httpClient = new HttpClient())
using (var httpResponseMessage = await httpClient.GetAsync(url))
{
if (httpResponseMessage.IsSuccessStatusCode)
return await httpResponseMessage.Content.ReadAsStringAsync();
}
//TODO: log httpResponseMessage.StatusCode
return null;
}
private static async Task SaveSnapshots(IEnumerable<Snapshot> snapshots)
{
foreach (var snapshot in snapshots)
try
{
await snapshot.AddOrUpdateRules();
}
catch (Exception)
{
//TODO: log exception
}
}
}
}

View file

@ -1,69 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using FilterLists.Data;
using FilterLists.Data.Entities;
using FilterLists.Data.Entities.Junctions;
namespace FilterLists.Services.ScrapeService
{
public class Snapshot
{
private readonly FilterListsDbContext dbContext;
private readonly int filterListId;
private List<string> rawRules;
public Snapshot(FilterListsDbContext dbContext, int filterListId, string content)
{
this.dbContext = dbContext;
this.filterListId = filterListId;
ParseContent(content);
}
public bool HasRules => rawRules != null;
private void ParseContent(string content)
{
if (content == null) return;
rawRules = content.Split(new[] {"\r\n", "\r", "\n"}, StringSplitOptions.RemoveEmptyEntries).ToList();
for (var i = 0; i < rawRules.Count; i++)
rawRules[i] = rawRules[i].LintStringForMySql();
rawRules = rawRules.Where(x => !string.IsNullOrWhiteSpace(x)).ToList();
}
//TODO: split large lists into batches
public async Task AddOrUpdateRules()
{
dbContext.ChangeTracker.AutoDetectChangesEnabled = false;
// add new Rules
var preExistingSnapshotRules = dbContext.Rules.Where(x => rawRules.Contains(x.Raw));
var newSnapshotRawRules = rawRules.Except(preExistingSnapshotRules.Select(x => x.Raw));
var newSnapshotRules = newSnapshotRawRules.Select(newSnapshotRawRule => new Rule {Raw = newSnapshotRawRule}).ToList();
await dbContext.Rules.AddRangeAsync(newSnapshotRules);
// remove deleted FilterListRules
var preExistingFilterListRules = dbContext.FilterListRules.Where(x => x.FilterListId == filterListId);
var deletedFilterListRules = preExistingFilterListRules.Where(x => !preExistingSnapshotRules.Select(y => y.Id).Contains(x.RuleId));
dbContext.FilterListRules.RemoveRange(deletedFilterListRules);
// add FilterListRules for pre-existing Rules
var preExistingSnapshotFilterListRules = preExistingSnapshotRules.Select(newSnapshotRule =>
new FilterListRule {FilterListId = filterListId, Rule = newSnapshotRule}).ToList()
.Except(preExistingFilterListRules).ToList();
await dbContext.FilterListRules.AddRangeAsync(preExistingSnapshotFilterListRules);
// add new FilterListRules
var newFilterListRules = newSnapshotRules.Select(newSnapshotRule =>
new FilterListRule {FilterListId = filterListId, Rule = newSnapshotRule}).ToList();
await dbContext.FilterListRules.AddRangeAsync(newFilterListRules);
// update scrapes
await dbContext.SaveChangesAsync();
}
}
}

View file

@ -1,4 +1,4 @@
namespace FilterLists.Services.ScrapeService
namespace FilterLists.Services.SnapshotService
{
public class FilterListViewUrlDto
{

View file

@ -1,4 +1,4 @@
namespace FilterLists.Services.ScrapeService
namespace FilterLists.Services.SnapshotService
{
public static class RawRuleLinterExtensions
{

View file

@ -0,0 +1,99 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using FilterLists.Data;
using FilterLists.Data.Entities;
using FilterLists.Data.Entities.Junctions;
namespace FilterLists.Services.SnapshotService
{
public class SnapshotDomainEntity
{
private readonly FilterListsDbContext dbContext;
private readonly FilterListViewUrlDto list;
private IEnumerable<Rule> newSnapshotRules;
private IEnumerable<Rule> preExistingSnapshotRules;
private List<string> rawRules;
private Snapshot snapshot;
public SnapshotDomainEntity(FilterListsDbContext dbContext, FilterListViewUrlDto list)
{
this.dbContext = dbContext;
this.list = list;
}
public async Task SaveSnapshot()
{
await AddSnapshot();
var content = await TryGetContent();
if (content != null)
{
ParseRawRules(content);
await AddNewRules();
AddSnapshotRules();
}
await dbContext.SaveChangesAsync();
}
private async Task AddSnapshot()
{
snapshot = new Snapshot {FilterList = await dbContext.FilterLists.FindAsync(list.Id)};
dbContext.Snapshots.Add(snapshot);
}
private async Task<string> TryGetContent()
{
try
{
return await GetContent();
}
catch (WebException we)
{
snapshot.HttpStatusCode = (int)((HttpWebResponse)we.Response).StatusCode;
return null;
}
}
private async Task<string> GetContent()
{
using (var httpClient = new HttpClient())
using (var httpResponseMessage = await httpClient.GetAsync(list.ViewUrl))
{
snapshot.HttpStatusCode = (int)httpResponseMessage.StatusCode;
if (httpResponseMessage.IsSuccessStatusCode)
return await httpResponseMessage.Content.ReadAsStringAsync();
}
return null;
}
private void ParseRawRules(string content)
{
rawRules = content.Split(new[] {"\r\n", "\r", "\n"}, StringSplitOptions.RemoveEmptyEntries).ToList();
for (var i = 0; i < rawRules.Count; i++)
rawRules[i] = rawRules[i].LintStringForMySql();
rawRules = rawRules.Where(x => !string.IsNullOrWhiteSpace(x)).ToList();
}
private async Task AddNewRules()
{
preExistingSnapshotRules = dbContext.Rules.Where(rule => rawRules.Contains(rule.Raw));
var newSnapshotRawRules = rawRules.Except(preExistingSnapshotRules.Select(x => x.Raw));
newSnapshotRules = newSnapshotRawRules.Select(newSnapshotRawRule => new Rule {Raw = newSnapshotRawRule})
.ToList();
await dbContext.Rules.AddRangeAsync(newSnapshotRules);
}
private void AddSnapshotRules()
{
snapshot.SnapshotRules = preExistingSnapshotRules
.Concat(newSnapshotRules)
.Select(rule => new SnapshotRule {Rule = rule, Snapshot = snapshot})
.ToList();
}
}
}

View file

@ -0,0 +1,68 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using AutoMapper.QueryableExtensions;
using FilterLists.Data;
using Microsoft.EntityFrameworkCore;
namespace FilterLists.Services.SnapshotService
{
public class SnapshotService
{
private readonly FilterListsDbContext dbContext;
public SnapshotService(FilterListsDbContext dbContext)
{
this.dbContext = dbContext;
}
//TODO: call via scheduled job
public async Task CaptureSnapshotsAsync(int batchSize)
{
var lists = await GetNextListsToCapture(batchSize);
var snapshots = GetSnapshots(lists);
await SaveSnapshots(snapshots);
}
private async Task<IEnumerable<FilterListViewUrlDto>> GetNextListsToCapture(int batchSize)
{
var neverCapturedLists = await GetNeverCapturedLists(batchSize);
if (neverCapturedLists.Count == batchSize)
return neverCapturedLists;
var leastRecentlyCapturedLists = await GetLeastRecentlyCapturedLists(batchSize - neverCapturedLists.Count);
return neverCapturedLists.Concat(leastRecentlyCapturedLists);
}
private async Task<List<FilterListViewUrlDto>> GetNeverCapturedLists(int batchSize)
{
return await dbContext.FilterLists
.Where(x => x.Snapshots.Count == 0)
.Take(batchSize)
.ProjectTo<FilterListViewUrlDto>()
.ToListAsync();
}
private async Task<List<FilterListViewUrlDto>> GetLeastRecentlyCapturedLists(int batchSize)
{
return await dbContext.Snapshots
.GroupBy(x => x.FilterList)
.Select(x => x.OrderByDescending(y => y.CreatedDateUtc).First())
.OrderBy(x => x.CreatedDateUtc)
.Select(x => x.FilterList)
.Take(batchSize)
.ProjectTo<FilterListViewUrlDto>()
.ToListAsync();
}
private IEnumerable<SnapshotDomainEntity> GetSnapshots(IEnumerable<FilterListViewUrlDto> lists)
{
return lists.Select(list => new SnapshotDomainEntity(dbContext, list));
}
private static async Task SaveSnapshots(IEnumerable<SnapshotDomainEntity> snapshots)
{
foreach (var snapshot in snapshots)
await snapshot.SaveSnapshot();
}
}
}