refactor: Refactor syncing Connections puzzles
This commit is contained in:
@@ -5,15 +5,15 @@ namespace ConnectionsAPI.Database
|
|||||||
{
|
{
|
||||||
public class ConnectionsContext(DbContextOptions<ConnectionsContext> dbContextOptions) : DbContext(dbContextOptions)
|
public class ConnectionsContext(DbContextOptions<ConnectionsContext> dbContextOptions) : DbContext(dbContextOptions)
|
||||||
{
|
{
|
||||||
public DbSet<Puzzle> Puzzles { get; set; }
|
public DbSet<CategoriesPuzzle> CategoriesPuzzles { get; set; }
|
||||||
|
|
||||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||||
{
|
{
|
||||||
modelBuilder.Entity<Puzzle>()
|
modelBuilder.Entity<CategoriesPuzzle>()
|
||||||
.HasIndex(x => x.PrintDate).IsUnique();
|
.HasIndex(x => x.PrintDate).IsUnique();
|
||||||
modelBuilder.Entity<Puzzle>()
|
modelBuilder.Entity<CategoriesPuzzle>()
|
||||||
.Ignore(x => x.NextPrintDate);
|
.Ignore(x => x.NextPrintDate);
|
||||||
modelBuilder.Entity<Puzzle>()
|
modelBuilder.Entity<CategoriesPuzzle>()
|
||||||
.Ignore(x => x.PrevPrintDate);
|
.Ignore(x => x.PrevPrintDate);
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
namespace ConnectionsAPI.Database.Entities
|
namespace ConnectionsAPI.Database.Entities
|
||||||
{
|
{
|
||||||
public class PuzzleCard
|
public class CategoriesCard
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Primary key of the entity
|
/// Primary key of the entity
|
||||||
@@ -20,11 +20,11 @@
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The ID of the associated Connections category
|
/// The ID of the associated Connections category
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int PuzzleCategoryId { get; set; }
|
public int CategoriesCategoryId { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The associated category instance
|
/// The associated category instance
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual PuzzleCategory? PuzzleCategory { get; set; }
|
public virtual CategoriesCategory? Category { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
namespace ConnectionsAPI.Database.Entities
|
namespace ConnectionsAPI.Database.Entities
|
||||||
{
|
{
|
||||||
public class PuzzleCategory
|
public class CategoriesCategory
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Primary key of the entity
|
/// Primary key of the entity
|
||||||
@@ -15,21 +15,21 @@
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The color of the category in this Connections puzzle; Also used for sorting
|
/// The color of the category in this Connections puzzle; Also used for sorting
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public PuzzleCategoryColor Color { get; set; }
|
public CategoriesColor Color { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The ID of the associated Connections puzzle
|
/// The ID of the associated Connections puzzle
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int PuzzleId { get; set; }
|
public int CategoriesPuzzleId { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The associated puzzle instance
|
/// The associated puzzle instance
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual Puzzle? Puzzle { get; set; }
|
public virtual CategoriesPuzzle? CategoriesPuzzle { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The cards associated with this category
|
/// The cards associated with this category
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ICollection<PuzzleCard> PuzzleCards { get; set; } = [];
|
public ICollection<CategoriesCard> CategoriesPuzzleCards { get; set; } = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
namespace ConnectionsAPI.Database.Entities
|
namespace ConnectionsAPI.Database.Entities
|
||||||
{
|
{
|
||||||
public enum PuzzleCategoryColor
|
public enum CategoriesColor
|
||||||
{
|
{
|
||||||
Yellow = 1,
|
Yellow = 1,
|
||||||
Green = 2,
|
Green = 2,
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
namespace ConnectionsAPI.Database.Entities
|
namespace ConnectionsAPI.Database.Entities
|
||||||
{
|
{
|
||||||
public class Puzzle
|
public class CategoriesPuzzle
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Primary key of the entity
|
/// Primary key of the entity
|
||||||
@@ -37,7 +37,7 @@ namespace ConnectionsAPI.Database.Entities
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The categories associated with this puzzle
|
/// The categories associated with this puzzle
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual ICollection<PuzzleCategory> Categories { get; set; } = [];
|
public virtual ICollection<CategoriesCategory> Categories { get; set; } = [];
|
||||||
|
|
||||||
[NotMapped]
|
[NotMapped]
|
||||||
public string? PrevPrintDate { get; set; }
|
public string? PrevPrintDate { get; set; }
|
||||||
135
Database/Migrations/20241226082514_RenameCategoriesPuzzles.Designer.cs
generated
Normal file
135
Database/Migrations/20241226082514_RenameCategoriesPuzzles.Designer.cs
generated
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using ConnectionsAPI.Database;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace ConnectionsAPI.Database.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(ConnectionsContext))]
|
||||||
|
[Migration("20241226082514_RenameCategoriesPuzzles")]
|
||||||
|
partial class RenameCategoriesPuzzles
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder.HasAnnotation("ProductVersion", "8.0.4");
|
||||||
|
|
||||||
|
modelBuilder.Entity("ConnectionsAPI.Database.Entities.CategoriesCard", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("CategoriesCategoryId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Content")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("Position")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("CategoriesCategoryId");
|
||||||
|
|
||||||
|
b.ToTable("CategoriesCard");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ConnectionsAPI.Database.Entities.CategoriesCategory", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("CategoriesPuzzleId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Color")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("CategoriesPuzzleId");
|
||||||
|
|
||||||
|
b.ToTable("CategoriesCategory");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ConnectionsAPI.Database.Entities.CategoriesPuzzle", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("ContentMD5")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedDate")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("EditorName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("Index")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("PrintDate")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("PrintDate")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("CategoriesPuzzles");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ConnectionsAPI.Database.Entities.CategoriesCard", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("ConnectionsAPI.Database.Entities.CategoriesCategory", "Category")
|
||||||
|
.WithMany("CategoriesPuzzleCards")
|
||||||
|
.HasForeignKey("CategoriesCategoryId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Category");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ConnectionsAPI.Database.Entities.CategoriesCategory", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("ConnectionsAPI.Database.Entities.CategoriesPuzzle", "CategoriesPuzzle")
|
||||||
|
.WithMany("Categories")
|
||||||
|
.HasForeignKey("CategoriesPuzzleId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("CategoriesPuzzle");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ConnectionsAPI.Database.Entities.CategoriesCategory", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("CategoriesPuzzleCards");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ConnectionsAPI.Database.Entities.CategoriesPuzzle", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Categories");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
187
Database/Migrations/20241226082514_RenameCategoriesPuzzles.cs
Normal file
187
Database/Migrations/20241226082514_RenameCategoriesPuzzles.cs
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace ConnectionsAPI.Database.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class RenameCategoriesPuzzles : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "PuzzleCard");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "PuzzleCategory");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Puzzles");
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "CategoriesPuzzles",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
|
CreatedDate = table.Column<DateTime>(type: "TEXT", nullable: false),
|
||||||
|
PrintDate = table.Column<string>(type: "TEXT", nullable: false),
|
||||||
|
EditorName = table.Column<string>(type: "TEXT", nullable: false),
|
||||||
|
Index = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
ContentMD5 = table.Column<string>(type: "TEXT", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_CategoriesPuzzles", x => x.Id);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "CategoriesCategory",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
|
Name = table.Column<string>(type: "TEXT", nullable: false),
|
||||||
|
Color = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
CategoriesPuzzleId = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_CategoriesCategory", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_CategoriesCategory_CategoriesPuzzles_CategoriesPuzzleId",
|
||||||
|
column: x => x.CategoriesPuzzleId,
|
||||||
|
principalTable: "CategoriesPuzzles",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "CategoriesCard",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
|
Content = table.Column<string>(type: "TEXT", nullable: false),
|
||||||
|
Position = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
CategoriesCategoryId = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_CategoriesCard", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_CategoriesCard_CategoriesCategory_CategoriesCategoryId",
|
||||||
|
column: x => x.CategoriesCategoryId,
|
||||||
|
principalTable: "CategoriesCategory",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_CategoriesCard_CategoriesCategoryId",
|
||||||
|
table: "CategoriesCard",
|
||||||
|
column: "CategoriesCategoryId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_CategoriesCategory_CategoriesPuzzleId",
|
||||||
|
table: "CategoriesCategory",
|
||||||
|
column: "CategoriesPuzzleId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_CategoriesPuzzles_PrintDate",
|
||||||
|
table: "CategoriesPuzzles",
|
||||||
|
column: "PrintDate",
|
||||||
|
unique: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "CategoriesCard");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "CategoriesCategory");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "CategoriesPuzzles");
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Puzzles",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
|
ContentMD5 = table.Column<string>(type: "TEXT", nullable: false),
|
||||||
|
CreatedDate = table.Column<DateTime>(type: "TEXT", nullable: false),
|
||||||
|
EditorName = table.Column<string>(type: "TEXT", nullable: false),
|
||||||
|
Index = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
PrintDate = table.Column<string>(type: "TEXT", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Puzzles", x => x.Id);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "PuzzleCategory",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
|
PuzzleId = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
Color = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
Name = table.Column<string>(type: "TEXT", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_PuzzleCategory", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_PuzzleCategory_Puzzles_PuzzleId",
|
||||||
|
column: x => x.PuzzleId,
|
||||||
|
principalTable: "Puzzles",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "PuzzleCard",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
|
PuzzleCategoryId = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
Content = table.Column<string>(type: "TEXT", nullable: false),
|
||||||
|
Position = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_PuzzleCard", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_PuzzleCard_PuzzleCategory_PuzzleCategoryId",
|
||||||
|
column: x => x.PuzzleCategoryId,
|
||||||
|
principalTable: "PuzzleCategory",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_PuzzleCard_PuzzleCategoryId",
|
||||||
|
table: "PuzzleCard",
|
||||||
|
column: "PuzzleCategoryId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_PuzzleCategory_PuzzleId",
|
||||||
|
table: "PuzzleCategory",
|
||||||
|
column: "PuzzleId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Puzzles_PrintDate",
|
||||||
|
table: "Puzzles",
|
||||||
|
column: "PrintDate",
|
||||||
|
unique: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,7 +17,53 @@ namespace ConnectionsAPI.Database.Migrations
|
|||||||
#pragma warning disable 612, 618
|
#pragma warning disable 612, 618
|
||||||
modelBuilder.HasAnnotation("ProductVersion", "8.0.4");
|
modelBuilder.HasAnnotation("ProductVersion", "8.0.4");
|
||||||
|
|
||||||
modelBuilder.Entity("ConnectionsAPI.Database.Entities.Puzzle", b =>
|
modelBuilder.Entity("ConnectionsAPI.Database.Entities.CategoriesCard", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("CategoriesCategoryId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Content")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("Position")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("CategoriesCategoryId");
|
||||||
|
|
||||||
|
b.ToTable("CategoriesCard");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ConnectionsAPI.Database.Entities.CategoriesCategory", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("CategoriesPuzzleId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Color")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("CategoriesPuzzleId");
|
||||||
|
|
||||||
|
b.ToTable("CategoriesCategory");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ConnectionsAPI.Database.Entities.CategoriesPuzzle", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
@@ -46,86 +92,40 @@ namespace ConnectionsAPI.Database.Migrations
|
|||||||
b.HasIndex("PrintDate")
|
b.HasIndex("PrintDate")
|
||||||
.IsUnique();
|
.IsUnique();
|
||||||
|
|
||||||
b.ToTable("Puzzles");
|
b.ToTable("CategoriesPuzzles");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("ConnectionsAPI.Database.Entities.PuzzleCard", b =>
|
modelBuilder.Entity("ConnectionsAPI.Database.Entities.CategoriesCard", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.HasOne("ConnectionsAPI.Database.Entities.CategoriesCategory", "Category")
|
||||||
.ValueGeneratedOnAdd()
|
.WithMany("CategoriesPuzzleCards")
|
||||||
.HasColumnType("INTEGER");
|
.HasForeignKey("CategoriesCategoryId")
|
||||||
|
|
||||||
b.Property<string>("Content")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<int>("Position")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<int>("PuzzleCategoryId")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasIndex("PuzzleCategoryId");
|
|
||||||
|
|
||||||
b.ToTable("PuzzleCard");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("ConnectionsAPI.Database.Entities.PuzzleCategory", b =>
|
|
||||||
{
|
|
||||||
b.Property<int>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<int>("Color")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<string>("Name")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<int>("PuzzleId")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasIndex("PuzzleId");
|
|
||||||
|
|
||||||
b.ToTable("PuzzleCategory");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("ConnectionsAPI.Database.Entities.PuzzleCard", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("ConnectionsAPI.Database.Entities.PuzzleCategory", "PuzzleCategory")
|
|
||||||
.WithMany("PuzzleCards")
|
|
||||||
.HasForeignKey("PuzzleCategoryId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
b.Navigation("PuzzleCategory");
|
b.Navigation("Category");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("ConnectionsAPI.Database.Entities.PuzzleCategory", b =>
|
modelBuilder.Entity("ConnectionsAPI.Database.Entities.CategoriesCategory", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("ConnectionsAPI.Database.Entities.Puzzle", "Puzzle")
|
b.HasOne("ConnectionsAPI.Database.Entities.CategoriesPuzzle", "CategoriesPuzzle")
|
||||||
.WithMany("Categories")
|
.WithMany("Categories")
|
||||||
.HasForeignKey("PuzzleId")
|
.HasForeignKey("CategoriesPuzzleId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
b.Navigation("Puzzle");
|
b.Navigation("CategoriesPuzzle");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("ConnectionsAPI.Database.Entities.Puzzle", b =>
|
modelBuilder.Entity("ConnectionsAPI.Database.Entities.CategoriesCategory", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("CategoriesPuzzleCards");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ConnectionsAPI.Database.Entities.CategoriesPuzzle", b =>
|
||||||
{
|
{
|
||||||
b.Navigation("Categories");
|
b.Navigation("Categories");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("ConnectionsAPI.Database.Entities.PuzzleCategory", b =>
|
|
||||||
{
|
|
||||||
b.Navigation("PuzzleCards");
|
|
||||||
});
|
|
||||||
#pragma warning restore 612, 618
|
#pragma warning restore 612, 618
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,16 +7,16 @@ namespace ConnectionsAPI.Database.Repository
|
|||||||
{
|
{
|
||||||
private readonly ConnectionsContext _db = _db;
|
private readonly ConnectionsContext _db = _db;
|
||||||
|
|
||||||
public async Task<Puzzle?> GetPuzzleByDateAsync(string printDate, bool includeSolutions = true)
|
public async Task<CategoriesPuzzle?> GetPuzzleByDateAsync(string printDate, bool includeSolutions = true)
|
||||||
{
|
{
|
||||||
// query for the puzzle
|
// query for the puzzle
|
||||||
var query = _db.Puzzles.AsNoTracking();
|
var query = _db.CategoriesPuzzles.AsNoTracking();
|
||||||
|
|
||||||
if (includeSolutions)
|
if (includeSolutions)
|
||||||
{
|
{
|
||||||
query = query
|
query = query
|
||||||
.Include(x => x.Categories)
|
.Include(x => x.Categories)
|
||||||
.ThenInclude(x => x.PuzzleCards);
|
.ThenInclude(x => x.CategoriesPuzzleCards);
|
||||||
}
|
}
|
||||||
|
|
||||||
var puzzle = await query.FirstOrDefaultAsync(x => x.PrintDate == printDate);
|
var puzzle = await query.FirstOrDefaultAsync(x => x.PrintDate == printDate);
|
||||||
@@ -32,17 +32,17 @@ namespace ConnectionsAPI.Database.Repository
|
|||||||
return puzzle;
|
return puzzle;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IEnumerable<Puzzle>> GetAllPuzzlesAsync(bool includeSolutions = true)
|
public async Task<IEnumerable<CategoriesPuzzle>> GetAllPuzzlesAsync(bool includeSolutions = true)
|
||||||
{
|
{
|
||||||
// query all, ordered by print date
|
// query all, ordered by print date
|
||||||
var query = _db.Puzzles
|
var query = _db.CategoriesPuzzles
|
||||||
.AsNoTracking();
|
.AsNoTracking();
|
||||||
|
|
||||||
if (includeSolutions)
|
if (includeSolutions)
|
||||||
{
|
{
|
||||||
query = query
|
query = query
|
||||||
.Include(x => x.Categories)
|
.Include(x => x.Categories)
|
||||||
.ThenInclude(x => x.PuzzleCards);
|
.ThenInclude(x => x.CategoriesPuzzleCards);
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = (await query.OrderBy(x => x.PrintDate).ToListAsync()) ?? [];
|
var result = (await query.OrderBy(x => x.PrintDate).ToListAsync()) ?? [];
|
||||||
@@ -55,15 +55,15 @@ namespace ConnectionsAPI.Database.Repository
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task EnhancePuzzleWithDatesAsync(Puzzle puzzle)
|
private async Task EnhancePuzzleWithDatesAsync(CategoriesPuzzle puzzle)
|
||||||
{
|
{
|
||||||
string? previousPuzzleDate = await _db.Puzzles.AsNoTracking()
|
string? previousPuzzleDate = await _db.CategoriesPuzzles.AsNoTracking()
|
||||||
.Where(x => x.PrintDate.CompareTo(puzzle.PrintDate) < 0)
|
.Where(x => x.PrintDate.CompareTo(puzzle.PrintDate) < 0)
|
||||||
.OrderByDescending(x => x.PrintDate)
|
.OrderByDescending(x => x.PrintDate)
|
||||||
.Select(x => x.PrintDate)
|
.Select(x => x.PrintDate)
|
||||||
.FirstOrDefaultAsync();
|
.FirstOrDefaultAsync();
|
||||||
|
|
||||||
string? nextPuzzleDate = await _db.Puzzles.AsNoTracking()
|
string? nextPuzzleDate = await _db.CategoriesPuzzles.AsNoTracking()
|
||||||
.Where(x => x.PrintDate.CompareTo(puzzle.PrintDate) > 0)
|
.Where(x => x.PrintDate.CompareTo(puzzle.PrintDate) > 0)
|
||||||
.OrderBy(x => x.PrintDate)
|
.OrderBy(x => x.PrintDate)
|
||||||
.Select(x => x.PrintDate)
|
.Select(x => x.PrintDate)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
{
|
{
|
||||||
public class PuzzleDTO
|
public class PuzzleDTO
|
||||||
{
|
{
|
||||||
public static PuzzleDTO FromEntity(Database.Entities.Puzzle dbPuzzle) =>
|
public static PuzzleDTO FromEntity(Database.Entities.CategoriesPuzzle dbPuzzle) =>
|
||||||
new()
|
new()
|
||||||
{
|
{
|
||||||
PuzzleNumber = dbPuzzle.Index,
|
PuzzleNumber = dbPuzzle.Index,
|
||||||
@@ -25,11 +25,11 @@
|
|||||||
|
|
||||||
public class PuzzleCategoryDTO
|
public class PuzzleCategoryDTO
|
||||||
{
|
{
|
||||||
public static PuzzleCategoryDTO FromEntity(Database.Entities.PuzzleCategory dbCategory) =>
|
public static PuzzleCategoryDTO FromEntity(Database.Entities.CategoriesCategory dbCategory) =>
|
||||||
new()
|
new()
|
||||||
{
|
{
|
||||||
Title = dbCategory.Name,
|
Title = dbCategory.Name,
|
||||||
Cards = dbCategory.PuzzleCards.OrderBy(x => x.Content).Select(PuzzleCardDTO.FromEntity).ToList(),
|
Cards = dbCategory.CategoriesPuzzleCards.OrderBy(x => x.Content).Select(PuzzleCardDTO.FromEntity).ToList(),
|
||||||
Color = dbCategory.Color.ToString().ToLower(),
|
Color = dbCategory.Color.ToString().ToLower(),
|
||||||
OrderingKey = (int)dbCategory.Color
|
OrderingKey = (int)dbCategory.Color
|
||||||
};
|
};
|
||||||
@@ -42,7 +42,7 @@
|
|||||||
|
|
||||||
public class PuzzleCardDTO
|
public class PuzzleCardDTO
|
||||||
{
|
{
|
||||||
public static PuzzleCardDTO FromEntity(Database.Entities.PuzzleCard dbCard) =>
|
public static PuzzleCardDTO FromEntity(Database.Entities.CategoriesCard dbCard) =>
|
||||||
new()
|
new()
|
||||||
{
|
{
|
||||||
Content = dbCard.Content,
|
Content = dbCard.Content,
|
||||||
|
|||||||
@@ -21,6 +21,9 @@ namespace ConnectionsAPI.Utility
|
|||||||
public string Editor { get; set; } = string.Empty;
|
public string Editor { get; set; } = string.Empty;
|
||||||
[JsonPropertyName("categories")]
|
[JsonPropertyName("categories")]
|
||||||
public IReadOnlyList<NYTConnectionsPuzzleCategory> Categories { get; set; } = [];
|
public IReadOnlyList<NYTConnectionsPuzzleCategory> Categories { get; set; } = [];
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public string Md5 { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
class NYTConnectionsPuzzleCategory
|
class NYTConnectionsPuzzleCategory
|
||||||
@@ -59,13 +62,26 @@ namespace ConnectionsAPI.Utility
|
|||||||
ConcurrentDictionary<string, string> responses = new();
|
ConcurrentDictionary<string, string> responses = new();
|
||||||
foreach (var batch in syncDates.Chunk(5))
|
foreach (var batch in syncDates.Chunk(5))
|
||||||
{
|
{
|
||||||
|
ConcurrentBag<NYTConnectionsPuzzle> batchPuzzles = [];
|
||||||
await Task.WhenAll(
|
await Task.WhenAll(
|
||||||
batch.Select(x => GetConnectionsResponseAsync(x, ct).ContinueWith(t =>
|
batch.Select(x => GetConnectionsResponseAsync(x, ct).ContinueWith(t =>
|
||||||
{
|
{
|
||||||
string? result = t.Result;
|
string? result = t.Result;
|
||||||
if (!string.IsNullOrWhiteSpace(result))
|
if (!string.IsNullOrWhiteSpace(result))
|
||||||
{
|
{
|
||||||
responses.TryAdd(x, result);
|
try
|
||||||
|
{
|
||||||
|
var nytResponseJson = JsonSerializer.Deserialize<NYTConnectionsPuzzle>(result)
|
||||||
|
?? throw new InvalidDataException("Connections response deserialized to null");
|
||||||
|
|
||||||
|
string md5 = HashUtility.CalculateMD5(result);
|
||||||
|
nytResponseJson.Md5 = md5;
|
||||||
|
batchPuzzles.Add(nytResponseJson);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Failed to deserialize Connections response for {date}", x);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -73,59 +89,44 @@ namespace ConnectionsAPI.Utility
|
|||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
// process the response data
|
foreach (var puzzle in batchPuzzles.OrderBy(x => x.PrintDate))
|
||||||
foreach (var response in responses.Select(kvp => new { PrintDate = kvp.Key, JsonContent = kvp.Value })
|
|
||||||
.OrderBy(x => x.PrintDate))
|
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Processing puzzle data for {printDate}", response.PrintDate);
|
await UpsertPuzzleDataAsync(puzzle);
|
||||||
await UpsertPuzzleDataAsync(response.PrintDate, response.JsonContent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await _db.SaveChangesAsync(ct);
|
await _db.SaveChangesAsync(ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task UpsertPuzzleDataAsync(string printDate, string puzzleJson)
|
|
||||||
{
|
|
||||||
// check if JSON is valid
|
|
||||||
NYTConnectionsPuzzle? nytPuzzle = JsonSerializer.Deserialize<NYTConnectionsPuzzle>(puzzleJson);
|
|
||||||
if (nytPuzzle == null || nytPuzzle.Status != "OK")
|
|
||||||
{
|
|
||||||
_logger.LogError("JSON content for {printDate} failed to deserialize or status not OK", printDate);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// calculate JSON content hash for change detection
|
private async Task UpsertPuzzleDataAsync(NYTConnectionsPuzzle nytPuzzle)
|
||||||
string jsonMD5 = HashUtility.CalculateMD5(puzzleJson);
|
{
|
||||||
|
|
||||||
// get a tracking reference to the puzzle matching by print date, either by querying or creating a new entity
|
// get a tracking reference to the puzzle matching by print date, either by querying or creating a new entity
|
||||||
var puzzle = await _db.Puzzles
|
var puzzle = await _db.CategoriesPuzzles
|
||||||
.Include(x => x.Categories)
|
.Include(x => x.Categories)
|
||||||
.ThenInclude(x => x.PuzzleCards)
|
.ThenInclude(x => x.CategoriesPuzzleCards)
|
||||||
.FirstOrDefaultAsync(x => x.PrintDate == printDate);
|
.FirstOrDefaultAsync(x => x.PrintDate == nytPuzzle.PrintDate);
|
||||||
if (puzzle == null)
|
if (puzzle == null)
|
||||||
{
|
{
|
||||||
_logger.LogTrace("No puzzle found for {printDate}, puzzle will be created", printDate);
|
_logger.LogTrace("No puzzle found for {printDate}, puzzle will be created", nytPuzzle.PrintDate);
|
||||||
puzzle = new Database.Entities.Puzzle
|
puzzle = new Database.Entities.CategoriesPuzzle
|
||||||
{
|
{
|
||||||
Categories = [],
|
Categories = [],
|
||||||
CreatedDate = DateTime.UtcNow
|
CreatedDate = DateTime.UtcNow
|
||||||
};
|
};
|
||||||
_db.Puzzles.Add(puzzle);
|
_db.CategoriesPuzzles.Add(puzzle);
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the content hash matches, no update needed
|
// if the content hash matches, no update needed
|
||||||
if (puzzle.ContentMD5 == jsonMD5)
|
if (puzzle.ContentMD5 == nytPuzzle.Md5)
|
||||||
{
|
{
|
||||||
_logger.LogTrace("JSON content hash for {printDate} matches, no need for update", printDate);
|
_logger.LogTrace("JSON content hash for {printDate} matches, no need for update", nytPuzzle.PrintDate);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
puzzle.ContentMD5 = jsonMD5;
|
puzzle.ContentMD5 = nytPuzzle.Md5;
|
||||||
puzzle.PrintDate = printDate;
|
puzzle.PrintDate = nytPuzzle.PrintDate;
|
||||||
puzzle.EditorName = nytPuzzle.Editor;
|
puzzle.EditorName = nytPuzzle.Editor;
|
||||||
puzzle.Index = CalculateConnectionsDayIndex(printDate);
|
puzzle.Index = CalculateConnectionsDayIndex(nytPuzzle.PrintDate);
|
||||||
puzzle.Categories ??= [];
|
puzzle.Categories ??= [];
|
||||||
|
|
||||||
// mark items for deletion and also remove them from here to be readded
|
// mark items for deletion and also remove them from here to be readded
|
||||||
@@ -136,23 +137,23 @@ namespace ConnectionsAPI.Utility
|
|||||||
int idx = 1;
|
int idx = 1;
|
||||||
foreach (var nytCategory in nytPuzzle.Categories)
|
foreach (var nytCategory in nytPuzzle.Categories)
|
||||||
{
|
{
|
||||||
PuzzleCategory category = new()
|
CategoriesCategory category = new()
|
||||||
{
|
{
|
||||||
Color = (PuzzleCategoryColor)idx++,
|
Color = (CategoriesColor)idx++,
|
||||||
Name = nytCategory.Title,
|
Name = nytCategory.Title,
|
||||||
Puzzle = puzzle,
|
CategoriesPuzzle = puzzle,
|
||||||
PuzzleCards = []
|
CategoriesPuzzleCards = []
|
||||||
};
|
};
|
||||||
|
|
||||||
foreach (var nytCard in nytCategory.Cards)
|
foreach (var nytCard in nytCategory.Cards)
|
||||||
{
|
{
|
||||||
PuzzleCard card = new()
|
CategoriesCard card = new()
|
||||||
{
|
{
|
||||||
Content = nytCard.Content,
|
Content = nytCard.Content,
|
||||||
Position = nytCard.Position,
|
Position = nytCard.Position,
|
||||||
PuzzleCategory = category,
|
Category = category,
|
||||||
};
|
};
|
||||||
category.PuzzleCards.Add(card);
|
category.CategoriesPuzzleCards.Add(card);
|
||||||
}
|
}
|
||||||
|
|
||||||
puzzle.Categories.Add(category);
|
puzzle.Categories.Add(category);
|
||||||
@@ -181,7 +182,7 @@ namespace ConnectionsAPI.Utility
|
|||||||
private async Task<IReadOnlyList<string>> GetSyncDatesAsync(CancellationToken ct)
|
private async Task<IReadOnlyList<string>> GetSyncDatesAsync(CancellationToken ct)
|
||||||
{
|
{
|
||||||
// query the last puzzle we have in the database
|
// query the last puzzle we have in the database
|
||||||
string? lastSyncedPuzzleDate = await _db.Puzzles.AsNoTracking()
|
string? lastSyncedPuzzleDate = await _db.CategoriesPuzzles.AsNoTracking()
|
||||||
.OrderByDescending(x => x.PrintDate)
|
.OrderByDescending(x => x.PrintDate)
|
||||||
.Select(x => x.PrintDate)
|
.Select(x => x.PrintDate)
|
||||||
.FirstOrDefaultAsync(cancellationToken: ct);
|
.FirstOrDefaultAsync(cancellationToken: ct);
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ConnectionStrings": {
|
"ConnectionStrings": {
|
||||||
"ConnectionsContext": "Data Source=c:\\tmp\\connections-api\\dev.db;"
|
"ConnectionsContext": "Data Source=.tmp/dev.db;"
|
||||||
},
|
},
|
||||||
"Sync": {
|
"Sync": {
|
||||||
"RunImmediately": true
|
"RunImmediately": true
|
||||||
|
|||||||
Reference in New Issue
Block a user