refactor: properly rename connections-related tables; top-level namespaces

This commit is contained in:
2024-12-26 13:49:26 +01:00
parent a1950b7586
commit feb47b1f8e
24 changed files with 967 additions and 495 deletions

View File

@@ -1,23 +1,22 @@
using ConnectionsAPI.Database.Entities; using ConnectionsAPI.Database.Entities;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace ConnectionsAPI.Database namespace ConnectionsAPI.Database;
{
public class ConnectionsContext(DbContextOptions<ConnectionsContext> dbContextOptions) : DbContext(dbContextOptions) public class ConnectionsContext(DbContextOptions<ConnectionsContext> dbContextOptions) : DbContext(dbContextOptions)
{ {
public DbSet<CategoriesPuzzle> CategoriesPuzzles { get; set; } public required DbSet<ConnectionsPuzzle> ConnectionsPuzzles { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder) protected override void OnModelCreating(ModelBuilder modelBuilder)
{ {
modelBuilder.Entity<CategoriesPuzzle>() modelBuilder.Entity<ConnectionsPuzzle>()
.HasIndex(x => x.PrintDate).IsUnique(); .HasIndex(x => x.PrintDate).IsUnique();
modelBuilder.Entity<CategoriesPuzzle>() modelBuilder.Entity<ConnectionsPuzzle>()
.Ignore(x => x.NextPrintDate); .Ignore(x => x.NextPrintDate);
modelBuilder.Entity<CategoriesPuzzle>() modelBuilder.Entity<ConnectionsPuzzle>()
.Ignore(x => x.PrevPrintDate); .Ignore(x => x.PrevPrintDate);
base.OnModelCreating(modelBuilder); base.OnModelCreating(modelBuilder);
} }
} }
}

View File

@@ -1,30 +0,0 @@
namespace ConnectionsAPI.Database.Entities
{
public class CategoriesCard
{
/// <summary>
/// Primary key of the entity
/// </summary>
public int Id { get; set; }
/// <summary>
/// The contents of this card (the word)
/// </summary>
public string Content { get; set; } = string.Empty;
/// <summary>
/// The initial position of this card on the grid
/// </summary>
public int Position { get; set; }
/// <summary>
/// The ID of the associated Connections category
/// </summary>
public int CategoriesCategoryId { get; set; }
/// <summary>
/// The associated category instance
/// </summary>
public virtual CategoriesCategory? Category { get; set; }
}
}

View File

@@ -1,35 +0,0 @@
namespace ConnectionsAPI.Database.Entities
{
public class CategoriesCategory
{
/// <summary>
/// Primary key of the entity
/// </summary>
public int Id { get; set; }
/// <summary>
/// The name of the category in this Connections puzzle
/// </summary>
public string Name { get; set; } = string.Empty;
/// <summary>
/// The color of the category in this Connections puzzle; Also used for sorting
/// </summary>
public CategoriesColor Color { get; set; }
/// <summary>
/// The ID of the associated Connections puzzle
/// </summary>
public int CategoriesPuzzleId { get; set; }
/// <summary>
/// The associated puzzle instance
/// </summary>
public virtual CategoriesPuzzle? CategoriesPuzzle { get; set; }
/// <summary>
/// The cards associated with this category
/// </summary>
public ICollection<CategoriesCard> CategoriesPuzzleCards { get; set; } = [];
}
}

View File

@@ -1,10 +0,0 @@
namespace ConnectionsAPI.Database.Entities
{
public enum CategoriesColor
{
Yellow = 1,
Green = 2,
Blue = 3,
Purple = 4,
}
}

View File

@@ -1,47 +0,0 @@
using System.ComponentModel.DataAnnotations.Schema;
namespace ConnectionsAPI.Database.Entities
{
public class CategoriesPuzzle
{
/// <summary>
/// Primary key of the entity
/// </summary>
public int Id { get; set; }
/// <summary>
/// When the entity was created (is the sync date)
/// </summary>
public DateTime CreatedDate { get; set; }
/// <summary>
/// When the puzzle was "printed" online
/// </summary>
public string PrintDate { get; set; } = string.Empty;
/// <summary>
/// The name of the editor for the puzzle
/// </summary>
public string EditorName { get; set; } = string.Empty;
/// <summary>
/// The actual count of the puzzle
/// </summary>
public int Index { get; set; }
/// <summary>
/// The MD5 hash for the source content used to sync this puzzle
/// </summary>
public string ContentMD5 { get; set; } = string.Empty;
/// <summary>
/// The categories associated with this puzzle
/// </summary>
public virtual ICollection<CategoriesCategory> Categories { get; set; } = [];
[NotMapped]
public string? PrevPrintDate { get; set; }
[NotMapped]
public string? NextPrintDate { get; set; }
}
}

View File

@@ -0,0 +1,29 @@
namespace ConnectionsAPI.Database.Entities;
public class ConnectionsCard
{
/// <summary>
/// Primary key of the entity
/// </summary>
public int Id { get; set; }
/// <summary>
/// The contents of this card (the word)
/// </summary>
public string Content { get; set; } = string.Empty;
/// <summary>
/// The initial position of this card on the grid
/// </summary>
public int Position { get; set; }
/// <summary>
/// The ID of the associated Connections category
/// </summary>
public int ConnectionsCategoryId { get; set; }
/// <summary>
/// The associated category instance
/// </summary>
public virtual ConnectionsCategory? Category { get; set; }
}

View File

@@ -0,0 +1,34 @@
namespace ConnectionsAPI.Database.Entities;
public class ConnectionsCategory
{
/// <summary>
/// Primary key of the entity
/// </summary>
public int Id { get; set; }
/// <summary>
/// The name of the category in this Connections puzzle
/// </summary>
public string Name { get; set; } = string.Empty;
/// <summary>
/// The color of the category in this Connections puzzle; Also used for sorting
/// </summary>
public ConnectionsColor Color { get; set; }
/// <summary>
/// The ID of the associated Connections puzzle
/// </summary>
public int ConnectionsPuzzleId { get; set; }
/// <summary>
/// The associated puzzle instance
/// </summary>
public virtual ConnectionsPuzzle? ConnectionsPuzzle { get; set; }
/// <summary>
/// The cards associated with this category
/// </summary>
public ICollection<ConnectionsCard> Cards { get; set; } = [];
}

View File

@@ -0,0 +1,10 @@
namespace ConnectionsAPI.Database.Entities;
public enum ConnectionsColor
{
Yellow = 1,
Green = 2,
Blue = 3,
Purple = 4,
}

View File

@@ -0,0 +1,45 @@
using System.ComponentModel.DataAnnotations.Schema;
namespace ConnectionsAPI.Database.Entities;
public class ConnectionsPuzzle
{
/// <summary>
/// Primary key of the entity
/// </summary>
public int Id { get; set; }
/// <summary>
/// When the entity was created (is the sync date)
/// </summary>
public DateTime CreatedDate { get; set; }
/// <summary>
/// When the puzzle was "printed" online
/// </summary>
public string PrintDate { get; set; } = string.Empty;
/// <summary>
/// The name of the editor for the puzzle
/// </summary>
public string EditorName { get; set; } = string.Empty;
/// <summary>
/// The actual count of the puzzle
/// </summary>
public int Index { get; set; }
/// <summary>
/// The MD5 hash for the source content used to sync this puzzle
/// </summary>
public string ContentMD5 { get; set; } = string.Empty;
/// <summary>
/// The categories associated with this puzzle
/// </summary>
public virtual ICollection<ConnectionsCategory> Categories { get; set; } = [];
[NotMapped]
public string? PrevPrintDate { get; set; }
[NotMapped]
public string? NextPrintDate { get; set; }
}

View File

@@ -0,0 +1,136 @@
// <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("20241226124356_RenameTablesCorrectly")]
partial class RenameTablesCorrectly
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "8.0.4");
modelBuilder.Entity("ConnectionsAPI.Database.Entities.ConnectionsCard", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("CategoriesCategoryId")
.HasColumnType("INTEGER");
b.Property<int?>("CategoryId")
.HasColumnType("INTEGER");
b.Property<string>("Content")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("Position")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("CategoryId");
b.ToTable("ConnectionsCard");
});
modelBuilder.Entity("ConnectionsAPI.Database.Entities.ConnectionsCategory", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("Color")
.HasColumnType("INTEGER");
b.Property<int>("ConnectionsPuzzleId")
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("ConnectionsPuzzleId");
b.ToTable("ConnectionsCategory");
});
modelBuilder.Entity("ConnectionsAPI.Database.Entities.ConnectionsPuzzle", 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.ConnectionsCard", b =>
{
b.HasOne("ConnectionsAPI.Database.Entities.ConnectionsCategory", "Category")
.WithMany("Cards")
.HasForeignKey("CategoryId");
b.Navigation("Category");
});
modelBuilder.Entity("ConnectionsAPI.Database.Entities.ConnectionsCategory", b =>
{
b.HasOne("ConnectionsAPI.Database.Entities.ConnectionsPuzzle", "ConnectionsPuzzle")
.WithMany("Categories")
.HasForeignKey("ConnectionsPuzzleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("ConnectionsPuzzle");
});
modelBuilder.Entity("ConnectionsAPI.Database.Entities.ConnectionsCategory", b =>
{
b.Navigation("Cards");
});
modelBuilder.Entity("ConnectionsAPI.Database.Entities.ConnectionsPuzzle", b =>
{
b.Navigation("Categories");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,134 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ConnectionsAPI.Database.Migrations
{
/// <inheritdoc />
public partial class RenameTablesCorrectly : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "CategoriesCard");
migrationBuilder.DropTable(
name: "CategoriesCategory");
migrationBuilder.CreateTable(
name: "ConnectionsCategory",
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),
ConnectionsPuzzleId = table.Column<int>(type: "INTEGER", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ConnectionsCategory", x => x.Id);
table.ForeignKey(
name: "FK_ConnectionsCategory_CategoriesPuzzles_ConnectionsPuzzleId",
column: x => x.ConnectionsPuzzleId,
principalTable: "CategoriesPuzzles",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "ConnectionsCard",
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),
CategoryId = table.Column<int>(type: "INTEGER", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_ConnectionsCard", x => x.Id);
table.ForeignKey(
name: "FK_ConnectionsCard_ConnectionsCategory_CategoryId",
column: x => x.CategoryId,
principalTable: "ConnectionsCategory",
principalColumn: "Id");
});
migrationBuilder.CreateIndex(
name: "IX_ConnectionsCard_CategoryId",
table: "ConnectionsCard",
column: "CategoryId");
migrationBuilder.CreateIndex(
name: "IX_ConnectionsCategory_ConnectionsPuzzleId",
table: "ConnectionsCategory",
column: "ConnectionsPuzzleId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "ConnectionsCard");
migrationBuilder.DropTable(
name: "ConnectionsCategory");
migrationBuilder.CreateTable(
name: "CategoriesCategory",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
CategoriesPuzzleId = 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_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),
CategoriesCategoryId = 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_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");
}
}
}

View File

@@ -0,0 +1,136 @@
// <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("20241226124529_RenameCollectionsTable")]
partial class RenameCollectionsTable
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "8.0.4");
modelBuilder.Entity("ConnectionsAPI.Database.Entities.ConnectionsCard", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("CategoriesCategoryId")
.HasColumnType("INTEGER");
b.Property<int?>("CategoryId")
.HasColumnType("INTEGER");
b.Property<string>("Content")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("Position")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("CategoryId");
b.ToTable("ConnectionsCard");
});
modelBuilder.Entity("ConnectionsAPI.Database.Entities.ConnectionsCategory", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("Color")
.HasColumnType("INTEGER");
b.Property<int>("ConnectionsPuzzleId")
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("ConnectionsPuzzleId");
b.ToTable("ConnectionsCategory");
});
modelBuilder.Entity("ConnectionsAPI.Database.Entities.ConnectionsPuzzle", 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("ConnectionsPuzzles");
});
modelBuilder.Entity("ConnectionsAPI.Database.Entities.ConnectionsCard", b =>
{
b.HasOne("ConnectionsAPI.Database.Entities.ConnectionsCategory", "Category")
.WithMany("Cards")
.HasForeignKey("CategoryId");
b.Navigation("Category");
});
modelBuilder.Entity("ConnectionsAPI.Database.Entities.ConnectionsCategory", b =>
{
b.HasOne("ConnectionsAPI.Database.Entities.ConnectionsPuzzle", "ConnectionsPuzzle")
.WithMany("Categories")
.HasForeignKey("ConnectionsPuzzleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("ConnectionsPuzzle");
});
modelBuilder.Entity("ConnectionsAPI.Database.Entities.ConnectionsCategory", b =>
{
b.Navigation("Cards");
});
modelBuilder.Entity("ConnectionsAPI.Database.Entities.ConnectionsPuzzle", b =>
{
b.Navigation("Categories");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,78 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ConnectionsAPI.Database.Migrations
{
/// <inheritdoc />
public partial class RenameCollectionsTable : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_ConnectionsCategory_CategoriesPuzzles_ConnectionsPuzzleId",
table: "ConnectionsCategory");
migrationBuilder.DropPrimaryKey(
name: "PK_CategoriesPuzzles",
table: "CategoriesPuzzles");
migrationBuilder.RenameTable(
name: "CategoriesPuzzles",
newName: "ConnectionsPuzzles");
migrationBuilder.RenameIndex(
name: "IX_CategoriesPuzzles_PrintDate",
table: "ConnectionsPuzzles",
newName: "IX_ConnectionsPuzzles_PrintDate");
migrationBuilder.AddPrimaryKey(
name: "PK_ConnectionsPuzzles",
table: "ConnectionsPuzzles",
column: "Id");
migrationBuilder.AddForeignKey(
name: "FK_ConnectionsCategory_ConnectionsPuzzles_ConnectionsPuzzleId",
table: "ConnectionsCategory",
column: "ConnectionsPuzzleId",
principalTable: "ConnectionsPuzzles",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_ConnectionsCategory_ConnectionsPuzzles_ConnectionsPuzzleId",
table: "ConnectionsCategory");
migrationBuilder.DropPrimaryKey(
name: "PK_ConnectionsPuzzles",
table: "ConnectionsPuzzles");
migrationBuilder.RenameTable(
name: "ConnectionsPuzzles",
newName: "CategoriesPuzzles");
migrationBuilder.RenameIndex(
name: "IX_ConnectionsPuzzles_PrintDate",
table: "CategoriesPuzzles",
newName: "IX_CategoriesPuzzles_PrintDate");
migrationBuilder.AddPrimaryKey(
name: "PK_CategoriesPuzzles",
table: "CategoriesPuzzles",
column: "Id");
migrationBuilder.AddForeignKey(
name: "FK_ConnectionsCategory_CategoriesPuzzles_ConnectionsPuzzleId",
table: "ConnectionsCategory",
column: "ConnectionsPuzzleId",
principalTable: "CategoriesPuzzles",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
}
}
}

View File

@@ -17,7 +17,7 @@ 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.CategoriesCard", b => modelBuilder.Entity("ConnectionsAPI.Database.Entities.ConnectionsCard", b =>
{ {
b.Property<int>("Id") b.Property<int>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
@@ -26,6 +26,9 @@ namespace ConnectionsAPI.Database.Migrations
b.Property<int>("CategoriesCategoryId") b.Property<int>("CategoriesCategoryId")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
b.Property<int?>("CategoryId")
.HasColumnType("INTEGER");
b.Property<string>("Content") b.Property<string>("Content")
.IsRequired() .IsRequired()
.HasColumnType("TEXT"); .HasColumnType("TEXT");
@@ -35,21 +38,21 @@ namespace ConnectionsAPI.Database.Migrations
b.HasKey("Id"); b.HasKey("Id");
b.HasIndex("CategoriesCategoryId"); b.HasIndex("CategoryId");
b.ToTable("CategoriesCard"); b.ToTable("ConnectionsCard");
}); });
modelBuilder.Entity("ConnectionsAPI.Database.Entities.CategoriesCategory", b => modelBuilder.Entity("ConnectionsAPI.Database.Entities.ConnectionsCategory", b =>
{ {
b.Property<int>("Id") b.Property<int>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
b.Property<int>("CategoriesPuzzleId") b.Property<int>("Color")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
b.Property<int>("Color") b.Property<int>("ConnectionsPuzzleId")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
b.Property<string>("Name") b.Property<string>("Name")
@@ -58,12 +61,12 @@ namespace ConnectionsAPI.Database.Migrations
b.HasKey("Id"); b.HasKey("Id");
b.HasIndex("CategoriesPuzzleId"); b.HasIndex("ConnectionsPuzzleId");
b.ToTable("CategoriesCategory"); b.ToTable("ConnectionsCategory");
}); });
modelBuilder.Entity("ConnectionsAPI.Database.Entities.CategoriesPuzzle", b => modelBuilder.Entity("ConnectionsAPI.Database.Entities.ConnectionsPuzzle", b =>
{ {
b.Property<int>("Id") b.Property<int>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
@@ -92,37 +95,35 @@ namespace ConnectionsAPI.Database.Migrations
b.HasIndex("PrintDate") b.HasIndex("PrintDate")
.IsUnique(); .IsUnique();
b.ToTable("CategoriesPuzzles"); b.ToTable("ConnectionsPuzzles");
}); });
modelBuilder.Entity("ConnectionsAPI.Database.Entities.CategoriesCard", b => modelBuilder.Entity("ConnectionsAPI.Database.Entities.ConnectionsCard", b =>
{ {
b.HasOne("ConnectionsAPI.Database.Entities.CategoriesCategory", "Category") b.HasOne("ConnectionsAPI.Database.Entities.ConnectionsCategory", "Category")
.WithMany("CategoriesPuzzleCards") .WithMany("Cards")
.HasForeignKey("CategoriesCategoryId") .HasForeignKey("CategoryId");
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Category"); b.Navigation("Category");
}); });
modelBuilder.Entity("ConnectionsAPI.Database.Entities.CategoriesCategory", b => modelBuilder.Entity("ConnectionsAPI.Database.Entities.ConnectionsCategory", b =>
{ {
b.HasOne("ConnectionsAPI.Database.Entities.CategoriesPuzzle", "CategoriesPuzzle") b.HasOne("ConnectionsAPI.Database.Entities.ConnectionsPuzzle", "ConnectionsPuzzle")
.WithMany("Categories") .WithMany("Categories")
.HasForeignKey("CategoriesPuzzleId") .HasForeignKey("ConnectionsPuzzleId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.Navigation("CategoriesPuzzle"); b.Navigation("ConnectionsPuzzle");
}); });
modelBuilder.Entity("ConnectionsAPI.Database.Entities.CategoriesCategory", b => modelBuilder.Entity("ConnectionsAPI.Database.Entities.ConnectionsCategory", b =>
{ {
b.Navigation("CategoriesPuzzleCards"); b.Navigation("Cards");
}); });
modelBuilder.Entity("ConnectionsAPI.Database.Entities.CategoriesPuzzle", b => modelBuilder.Entity("ConnectionsAPI.Database.Entities.ConnectionsPuzzle", b =>
{ {
b.Navigation("Categories"); b.Navigation("Categories");
}); });

View File

@@ -1,22 +1,22 @@
using ConnectionsAPI.Database.Entities; using ConnectionsAPI.Database.Entities;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace ConnectionsAPI.Database.Repository namespace ConnectionsAPI.Database.Repository;
{
public class PuzzleRepository(ConnectionsContext _db) public class PuzzleRepository(ConnectionsContext _db)
{ {
private readonly ConnectionsContext _db = _db; private readonly ConnectionsContext _db = _db;
public async Task<CategoriesPuzzle?> GetPuzzleByDateAsync(string printDate, bool includeSolutions = true) public async Task<ConnectionsPuzzle?> GetPuzzleByDateAsync(string printDate, bool includeSolutions = true)
{ {
// query for the puzzle // query for the puzzle
var query = _db.CategoriesPuzzles.AsNoTracking(); var query = _db.ConnectionsPuzzles.AsNoTracking();
if (includeSolutions) if (includeSolutions)
{ {
query = query query = query
.Include(x => x.Categories) .Include(x => x.Categories)
.ThenInclude(x => x.CategoriesPuzzleCards); .ThenInclude(x => x.Cards);
} }
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<CategoriesPuzzle>> GetAllPuzzlesAsync(bool includeSolutions = true) public async Task<IEnumerable<ConnectionsPuzzle>> GetAllPuzzlesAsync(bool includeSolutions = true)
{ {
// query all, ordered by print date // query all, ordered by print date
var query = _db.CategoriesPuzzles var query = _db.ConnectionsPuzzles
.AsNoTracking(); .AsNoTracking();
if (includeSolutions) if (includeSolutions)
{ {
query = query query = query
.Include(x => x.Categories) .Include(x => x.Categories)
.ThenInclude(x => x.CategoriesPuzzleCards); .ThenInclude(x => x.Cards);
} }
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(CategoriesPuzzle puzzle) private async Task EnhancePuzzleWithDatesAsync(ConnectionsPuzzle puzzle)
{ {
string? previousPuzzleDate = await _db.CategoriesPuzzles.AsNoTracking() string? previousPuzzleDate = await _db.ConnectionsPuzzles.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.CategoriesPuzzles.AsNoTracking() string? nextPuzzleDate = await _db.ConnectionsPuzzles.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)
@@ -73,4 +73,3 @@ namespace ConnectionsAPI.Database.Repository
puzzle.NextPrintDate = nextPuzzleDate; puzzle.NextPrintDate = nextPuzzleDate;
} }
} }
}

View File

@@ -3,19 +3,20 @@ using ConnectionsAPI.Database;
using ConnectionsAPI.Utility; using ConnectionsAPI.Utility;
using System.Diagnostics; using System.Diagnostics;
namespace ConnectionsAPI.Events namespace ConnectionsAPI.Events;
{
public class PuzzleSyncEvent : IEvent { }
public class PuzzleSyncHandler(ILogger<PuzzleSyncHandler> logger, IServiceScopeFactory scopeFactory) : IEventHandler<PuzzleSyncEvent> public class ConnectionsSyncEvent : IEvent { }
public class ConnectionsSyncHandler(ILogger<ConnectionsSyncHandler> logger,
IServiceScopeFactory scopeFactory) : IEventHandler<ConnectionsSyncEvent>
{ {
private readonly ILogger<PuzzleSyncHandler> _logger = logger; private readonly ILogger<ConnectionsSyncHandler> _logger = logger;
private readonly IServiceScopeFactory _scopeFactory = scopeFactory; private readonly IServiceScopeFactory _scopeFactory = scopeFactory;
public async Task HandleAsync(PuzzleSyncEvent eventModel, CancellationToken ct) public async Task HandleAsync(ConnectionsSyncEvent eventModel, CancellationToken ct)
{ {
Stopwatch stopwatch = Stopwatch.StartNew(); Stopwatch stopwatch = Stopwatch.StartNew();
_logger.LogInformation("Received Puzzle Sync Event"); _logger.LogInformation("Received Connections Sync Event");
try try
{ {
// construct scope // construct scope
@@ -29,13 +30,13 @@ namespace ConnectionsAPI.Events
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogError(ex, "Error while executing puzzle sync event"); _logger.LogError(ex, "Error while executing Connections sync event");
} }
finally finally
{ {
stopwatch.Stop(); stopwatch.Stop();
} }
_logger.LogInformation("Puzzle Sync Event finished in {ts}", stopwatch.Elapsed); _logger.LogInformation("Connections Sync Event finished in {ts}", stopwatch.Elapsed);
}
} }
} }

View File

@@ -21,7 +21,7 @@ namespace ConnectionsAPI.Features.Puzzle.Get
} }
} }
public class GetPuzzleEndpoint(PuzzleRepository puzzleRepo, ILogger<GetPuzzleEndpoint> logger, IAppCache cache) : Endpoint<GetPuzzleEndpointRequest, PuzzleDTO> public class GetPuzzleEndpoint(PuzzleRepository puzzleRepo, ILogger<GetPuzzleEndpoint> logger, IAppCache cache) : Endpoint<GetPuzzleEndpointRequest, ConnectionsPuzzleDTO>
{ {
private readonly PuzzleRepository _puzzleRepo = puzzleRepo; private readonly PuzzleRepository _puzzleRepo = puzzleRepo;
private readonly ILogger<GetPuzzleEndpoint> _logger = logger; private readonly ILogger<GetPuzzleEndpoint> _logger = logger;
@@ -58,7 +58,7 @@ namespace ConnectionsAPI.Features.Puzzle.Get
} }
// get response from cache // get response from cache
var response = PuzzleDTO.FromEntity(puzzle); var response = ConnectionsPuzzleDTO.FromEntity(puzzle);
// done // done
await SendAsync(response, cancellation: ct); await SendAsync(response, cancellation: ct);

View File

@@ -5,7 +5,7 @@ using Microsoft.EntityFrameworkCore;
namespace ConnectionsAPI.Features.Puzzle.List namespace ConnectionsAPI.Features.Puzzle.List
{ {
public class ListPuzzlesEndpoint(PuzzleRepository puzzleRepo, IAppCache cache) : EndpointWithoutRequest<ICollection<PuzzleDTO>> public class ListPuzzlesEndpoint(PuzzleRepository puzzleRepo, IAppCache cache) : EndpointWithoutRequest<ICollection<ConnectionsPuzzleDTO>>
{ {
private readonly PuzzleRepository _puzzleRepo = puzzleRepo; private readonly PuzzleRepository _puzzleRepo = puzzleRepo;
private readonly IAppCache _cache = cache; private readonly IAppCache _cache = cache;
@@ -25,7 +25,7 @@ namespace ConnectionsAPI.Features.Puzzle.List
var puzzles = await _puzzleRepo.GetAllPuzzlesAsync(includeSolutions: !hideSolutions); var puzzles = await _puzzleRepo.GetAllPuzzlesAsync(includeSolutions: !hideSolutions);
// map to response object // map to response object
var response = puzzles.Select(PuzzleDTO.FromEntity).ToList(); var response = puzzles.Select(ConnectionsPuzzleDTO.FromEntity).ToList();
// done // done
await SendAsync(response, cancellation: ct); await SendAsync(response, cancellation: ct);

View File

@@ -1,8 +1,8 @@
namespace ConnectionsAPI.Models namespace ConnectionsAPI.Models
{ {
public class PuzzleDTO public class ConnectionsPuzzleDTO
{ {
public static PuzzleDTO FromEntity(Database.Entities.CategoriesPuzzle dbPuzzle) => public static ConnectionsPuzzleDTO FromEntity(Database.Entities.ConnectionsPuzzle 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.CategoriesCategory dbCategory) => public static PuzzleCategoryDTO FromEntity(Database.Entities.ConnectionsCategory dbCategory) =>
new() new()
{ {
Title = dbCategory.Name, Title = dbCategory.Name,
Cards = dbCategory.CategoriesPuzzleCards.OrderBy(x => x.Content).Select(PuzzleCardDTO.FromEntity).ToList(), Cards = dbCategory.Cards.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.CategoriesCard dbCard) => public static PuzzleCardDTO FromEntity(Database.Entities.ConnectionsCard dbCard) =>
new() new()
{ {
Content = dbCard.Content, Content = dbCard.Content,

View File

@@ -56,7 +56,7 @@ namespace ConnectionsAPI
} }
private static Task SendSyncEvent(CancellationToken stoppingToken, bool wait = false) => private static Task SendSyncEvent(CancellationToken stoppingToken, bool wait = false) =>
new PuzzleSyncEvent { }.PublishAsync(wait ? Mode.WaitForAll : Mode.WaitForNone, stoppingToken); new ConnectionsSyncEvent { }.PublishAsync(wait ? Mode.WaitForAll : Mode.WaitForNone, stoppingToken);
private async Task WaitForNextSchedule(CronExpression cron, CancellationToken ct) private async Task WaitForNextSchedule(CronExpression cron, CancellationToken ct)
{ {

View File

@@ -1,8 +1,6 @@
namespace ConnectionsAPI.Utility namespace ConnectionsAPI.Utility;
{
public static class EnvironmentUtility public static class EnvironmentUtility
{ {
public static bool IsContainer => public static bool IsContainer =>
Environment.GetEnvironmentVariable("DOTNET_RUNNING_IN_CONTAINER") == "true"; Environment.GetEnvironmentVariable("DOTNET_RUNNING_IN_CONTAINER") == "true";
} }
}

View File

@@ -1,8 +1,7 @@
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Text; using System.Text;
namespace ConnectionsAPI.Utility namespace ConnectionsAPI.Utility;
{
public static class HashUtility public static class HashUtility
{ {
public static string CalculateMD5(string input) public static string CalculateMD5(string input)
@@ -11,4 +10,3 @@ namespace ConnectionsAPI.Utility
return Convert.ToHexString(hash).Replace("-", string.Empty).ToLower(); return Convert.ToHexString(hash).Replace("-", string.Empty).ToLower();
} }
} }
}

View File

@@ -6,8 +6,7 @@ using System.Globalization;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
namespace ConnectionsAPI.Utility namespace ConnectionsAPI.Utility;
{
public class SyncUtility(ConnectionsContext db, ILogger<SyncUtility> logger, HttpClient http) public class SyncUtility(ConnectionsContext db, ILogger<SyncUtility> logger, HttpClient http)
{ {
#region Response types #region Response types
@@ -101,19 +100,19 @@ namespace ConnectionsAPI.Utility
private async Task UpsertPuzzleDataAsync(NYTConnectionsPuzzle nytPuzzle) private async Task UpsertPuzzleDataAsync(NYTConnectionsPuzzle nytPuzzle)
{ {
// 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.CategoriesPuzzles var puzzle = await _db.ConnectionsPuzzles
.Include(x => x.Categories) .Include(x => x.Categories)
.ThenInclude(x => x.CategoriesPuzzleCards) .ThenInclude(x => x.Cards)
.FirstOrDefaultAsync(x => x.PrintDate == nytPuzzle.PrintDate); .FirstOrDefaultAsync(x => x.PrintDate == nytPuzzle.PrintDate);
if (puzzle == null) if (puzzle == null)
{ {
_logger.LogTrace("No puzzle found for {printDate}, puzzle will be created", nytPuzzle.PrintDate); _logger.LogTrace("No puzzle found for {printDate}, puzzle will be created", nytPuzzle.PrintDate);
puzzle = new Database.Entities.CategoriesPuzzle puzzle = new Database.Entities.ConnectionsPuzzle
{ {
Categories = [], Categories = [],
CreatedDate = DateTime.UtcNow CreatedDate = DateTime.UtcNow
}; };
_db.CategoriesPuzzles.Add(puzzle); _db.ConnectionsPuzzles.Add(puzzle);
} }
// if the content hash matches, no update needed // if the content hash matches, no update needed
@@ -137,23 +136,23 @@ namespace ConnectionsAPI.Utility
int idx = 1; int idx = 1;
foreach (var nytCategory in nytPuzzle.Categories) foreach (var nytCategory in nytPuzzle.Categories)
{ {
CategoriesCategory category = new() ConnectionsCategory category = new()
{ {
Color = (CategoriesColor)idx++, Color = (ConnectionsColor)idx++,
Name = nytCategory.Title, Name = nytCategory.Title,
CategoriesPuzzle = puzzle, ConnectionsPuzzle = puzzle,
CategoriesPuzzleCards = [] Cards = []
}; };
foreach (var nytCard in nytCategory.Cards) foreach (var nytCard in nytCategory.Cards)
{ {
CategoriesCard card = new() ConnectionsCard card = new()
{ {
Content = nytCard.Content, Content = nytCard.Content,
Position = nytCard.Position, Position = nytCard.Position,
Category = category, Category = category,
}; };
category.CategoriesPuzzleCards.Add(card); category.Cards.Add(card);
} }
puzzle.Categories.Add(category); puzzle.Categories.Add(category);
@@ -182,7 +181,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.CategoriesPuzzles.AsNoTracking() string? lastSyncedPuzzleDate = await _db.ConnectionsPuzzles.AsNoTracking()
.OrderByDescending(x => x.PrintDate) .OrderByDescending(x => x.PrintDate)
.Select(x => x.PrintDate) .Select(x => x.PrintDate)
.FirstOrDefaultAsync(cancellationToken: ct); .FirstOrDefaultAsync(cancellationToken: ct);
@@ -240,4 +239,3 @@ namespace ConnectionsAPI.Utility
return dates; return dates;
} }
} }
}

View File

@@ -1,5 +1,4 @@
namespace ConnectionsAPI.Utility namespace ConnectionsAPI.Utility;
{
public static class TimezoneUtility public static class TimezoneUtility
{ {
public static TimeZoneInfo? GetLatestTimezoneOnSystem() => public static TimeZoneInfo? GetLatestTimezoneOnSystem() =>
@@ -7,4 +6,3 @@
.OrderByDescending(x => x.GetUtcOffset(DateTime.UtcNow)) .OrderByDescending(x => x.GetUtcOffset(DateTime.UtcNow))
.FirstOrDefault(); .FirstOrDefault();
} }
}