From a65339395ba2741613eb5a9bccdefee8ddb0f8bf Mon Sep 17 00:00:00 2001 From: Daniel Harvey Date: Sun, 17 May 2026 23:11:44 -0400 Subject: [PATCH] Migrate DeepDrftContext from SQLite to PostgreSQL; add docker-compose for local Postgres 17 --- DeepDrftCli/DeepDrftCli.csproj | 16 +++---- DeepDrftCli/Program.cs | 2 +- DeepDrftModels/DeepDrftModels.csproj | 2 +- .../Data/DeepDrftContextFactory.cs | 2 +- .../DeepDrftWeb.Services.csproj | 12 ++++-- .../Migrations/20250904233927_Initial.cs | 41 ------------------ ....cs => 20260518025102_Initial.Designer.cs} | 29 ++++++++----- .../Migrations/20260518025102_Initial.cs | 42 +++++++++++++++++++ .../DeepDrftContextModelSnapshot.cs | 27 +++++++----- .../Repositories/TrackRepository.cs | 2 +- DeepDrftWeb/DeepDrftWeb.csproj | 10 ++--- DeepDrftWeb/Startup.cs | 2 +- DeepDrftWeb/appsettings.json | 2 +- docker-compose.yml | 14 +++++++ 14 files changed, 118 insertions(+), 85 deletions(-) delete mode 100644 DeepDrftWeb.Services/Migrations/20250904233927_Initial.cs rename DeepDrftWeb.Services/Migrations/{20250904233927_Initial.Designer.cs => 20260518025102_Initial.Designer.cs} (67%) create mode 100644 DeepDrftWeb.Services/Migrations/20260518025102_Initial.cs create mode 100644 docker-compose.yml diff --git a/DeepDrftCli/DeepDrftCli.csproj b/DeepDrftCli/DeepDrftCli.csproj index 1ca1e16..d3a9e31 100644 --- a/DeepDrftCli/DeepDrftCli.csproj +++ b/DeepDrftCli/DeepDrftCli.csproj @@ -8,14 +8,14 @@ - - - - - - - - + + + + + + + + diff --git a/DeepDrftCli/Program.cs b/DeepDrftCli/Program.cs index 117b010..a265998 100644 --- a/DeepDrftCli/Program.cs +++ b/DeepDrftCli/Program.cs @@ -22,7 +22,7 @@ builder.Services.AddLogging(configure => configure.AddConsole()); // Add database context builder.Services.AddDbContext(options => - options.UseSqlite(cliSettings.ConnectionString)); + options.UseNpgsql(cliSettings.ConnectionString)); // Add FileDatabase builder.Services.AddSingleton(provider => diff --git a/DeepDrftModels/DeepDrftModels.csproj b/DeepDrftModels/DeepDrftModels.csproj index cb70a35..f7f66b9 100644 --- a/DeepDrftModels/DeepDrftModels.csproj +++ b/DeepDrftModels/DeepDrftModels.csproj @@ -7,7 +7,7 @@ - + diff --git a/DeepDrftWeb.Services/Data/DeepDrftContextFactory.cs b/DeepDrftWeb.Services/Data/DeepDrftContextFactory.cs index f2f8bfd..d953860 100644 --- a/DeepDrftWeb.Services/Data/DeepDrftContextFactory.cs +++ b/DeepDrftWeb.Services/Data/DeepDrftContextFactory.cs @@ -8,7 +8,7 @@ public class DeepDrftContextFactory : IDesignTimeDbContextFactory(); - optionsBuilder.UseSqlite("Data Source=../Database/deepdrft.db"); + optionsBuilder.UseNpgsql("Host=localhost;Database=deepdrft_dev;Username=postgres;Password=postgres"); return new DeepDrftContext(optionsBuilder.Options); } diff --git a/DeepDrftWeb.Services/DeepDrftWeb.Services.csproj b/DeepDrftWeb.Services/DeepDrftWeb.Services.csproj index c923130..aee9b71 100644 --- a/DeepDrftWeb.Services/DeepDrftWeb.Services.csproj +++ b/DeepDrftWeb.Services/DeepDrftWeb.Services.csproj @@ -1,4 +1,4 @@ - + net10.0 @@ -7,12 +7,16 @@ - - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + diff --git a/DeepDrftWeb.Services/Migrations/20250904233927_Initial.cs b/DeepDrftWeb.Services/Migrations/20250904233927_Initial.cs deleted file mode 100644 index 0d937b3..0000000 --- a/DeepDrftWeb.Services/Migrations/20250904233927_Initial.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace DeepDrftWeb.Migrations -{ - /// - public partial class Initial : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "track", - columns: table => new - { - id = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - entry_key = table.Column(type: "TEXT", maxLength: 100, nullable: false), - track_name = table.Column(type: "TEXT", maxLength: 200, nullable: false), - artist = table.Column(type: "TEXT", maxLength: 200, nullable: false), - album = table.Column(type: "TEXT", maxLength: 200, nullable: true), - genre = table.Column(type: "TEXT", maxLength: 100, nullable: true), - release_date = table.Column(type: "TEXT", nullable: true), - image_path = table.Column(type: "TEXT", maxLength: 500, nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_track", x => x.id); - }); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "track"); - } - } -} diff --git a/DeepDrftWeb.Services/Migrations/20250904233927_Initial.Designer.cs b/DeepDrftWeb.Services/Migrations/20260518025102_Initial.Designer.cs similarity index 67% rename from DeepDrftWeb.Services/Migrations/20250904233927_Initial.Designer.cs rename to DeepDrftWeb.Services/Migrations/20260518025102_Initial.Designer.cs index b652a07..5301e98 100644 --- a/DeepDrftWeb.Services/Migrations/20250904233927_Initial.Designer.cs +++ b/DeepDrftWeb.Services/Migrations/20260518025102_Initial.Designer.cs @@ -5,63 +5,70 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; #nullable disable -namespace DeepDrftWeb.Migrations +namespace DeepDrftWeb.Services.Migrations { [DbContext(typeof(DeepDrftContext))] - [Migration("20250904233927_Initial")] + [Migration("20260518025102_Initial")] partial class Initial { /// protected override void BuildTargetModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "9.0.8"); + modelBuilder + .HasAnnotation("ProductVersion", "10.0.4") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); modelBuilder.Entity("DeepDrftModels.Entities.TrackEntity", b => { b.Property("Id") .ValueGeneratedOnAdd() - .HasColumnType("INTEGER") + .HasColumnType("bigint") .HasColumnName("id"); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + b.Property("Album") .HasMaxLength(200) - .HasColumnType("TEXT") + .HasColumnType("character varying(200)") .HasColumnName("album"); b.Property("Artist") .IsRequired() .HasMaxLength(200) - .HasColumnType("TEXT") + .HasColumnType("character varying(200)") .HasColumnName("artist"); b.Property("EntryKey") .IsRequired() .HasMaxLength(100) - .HasColumnType("TEXT") + .HasColumnType("character varying(100)") .HasColumnName("entry_key"); b.Property("Genre") .HasMaxLength(100) - .HasColumnType("TEXT") + .HasColumnType("character varying(100)") .HasColumnName("genre"); b.Property("ImagePath") .HasMaxLength(500) - .HasColumnType("TEXT") + .HasColumnType("character varying(500)") .HasColumnName("image_path"); b.Property("ReleaseDate") - .HasColumnType("TEXT") + .HasColumnType("date") .HasColumnName("release_date"); b.Property("TrackName") .IsRequired() .HasMaxLength(200) - .HasColumnType("TEXT") + .HasColumnType("character varying(200)") .HasColumnName("track_name"); b.HasKey("Id"); diff --git a/DeepDrftWeb.Services/Migrations/20260518025102_Initial.cs b/DeepDrftWeb.Services/Migrations/20260518025102_Initial.cs new file mode 100644 index 0000000..6735fda --- /dev/null +++ b/DeepDrftWeb.Services/Migrations/20260518025102_Initial.cs @@ -0,0 +1,42 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace DeepDrftWeb.Services.Migrations +{ + /// + public partial class Initial : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "track", + columns: table => new + { + id = table.Column(type: "bigint", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + entry_key = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + track_name = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + artist = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + album = table.Column(type: "character varying(200)", maxLength: 200, nullable: true), + genre = table.Column(type: "character varying(100)", maxLength: 100, nullable: true), + release_date = table.Column(type: "date", nullable: true), + image_path = table.Column(type: "character varying(500)", maxLength: 500, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_track", x => x.id); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "track"); + } + } +} diff --git a/DeepDrftWeb.Services/Migrations/DeepDrftContextModelSnapshot.cs b/DeepDrftWeb.Services/Migrations/DeepDrftContextModelSnapshot.cs index 36cb1f3..ca1c25b 100644 --- a/DeepDrftWeb.Services/Migrations/DeepDrftContextModelSnapshot.cs +++ b/DeepDrftWeb.Services/Migrations/DeepDrftContextModelSnapshot.cs @@ -4,10 +4,11 @@ using DeepDrftWeb.Services.Data; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; #nullable disable -namespace DeepDrftWeb.Migrations +namespace DeepDrftWeb.Services.Migrations { [DbContext(typeof(DeepDrftContext))] partial class DeepDrftContextModelSnapshot : ModelSnapshot @@ -15,50 +16,56 @@ namespace DeepDrftWeb.Migrations protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "9.0.8"); + modelBuilder + .HasAnnotation("ProductVersion", "10.0.4") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); modelBuilder.Entity("DeepDrftModels.Entities.TrackEntity", b => { b.Property("Id") .ValueGeneratedOnAdd() - .HasColumnType("INTEGER") + .HasColumnType("bigint") .HasColumnName("id"); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + b.Property("Album") .HasMaxLength(200) - .HasColumnType("TEXT") + .HasColumnType("character varying(200)") .HasColumnName("album"); b.Property("Artist") .IsRequired() .HasMaxLength(200) - .HasColumnType("TEXT") + .HasColumnType("character varying(200)") .HasColumnName("artist"); b.Property("EntryKey") .IsRequired() .HasMaxLength(100) - .HasColumnType("TEXT") + .HasColumnType("character varying(100)") .HasColumnName("entry_key"); b.Property("Genre") .HasMaxLength(100) - .HasColumnType("TEXT") + .HasColumnType("character varying(100)") .HasColumnName("genre"); b.Property("ImagePath") .HasMaxLength(500) - .HasColumnType("TEXT") + .HasColumnType("character varying(500)") .HasColumnName("image_path"); b.Property("ReleaseDate") - .HasColumnType("TEXT") + .HasColumnType("date") .HasColumnName("release_date"); b.Property("TrackName") .IsRequired() .HasMaxLength(200) - .HasColumnType("TEXT") + .HasColumnType("character varying(200)") .HasColumnName("track_name"); b.HasKey("Id"); diff --git a/DeepDrftWeb.Services/Repositories/TrackRepository.cs b/DeepDrftWeb.Services/Repositories/TrackRepository.cs index 69972a5..d4cb123 100644 --- a/DeepDrftWeb.Services/Repositories/TrackRepository.cs +++ b/DeepDrftWeb.Services/Repositories/TrackRepository.cs @@ -27,7 +27,7 @@ public class TrackRepository public async Task> GetPage(PagingParameters pageParameters) { // Two separate queries with no transaction: count and page can be momentarily inconsistent - // under concurrent writes. Acceptable — SQLite concurrency is low and the UI is read-only. + // under concurrent writes. Acceptable — write volume is low and the UI is read-only. // If filtering is added, the count query must be updated to apply the same filter. var count = await _db.Tracks.CountAsync(); diff --git a/DeepDrftWeb/DeepDrftWeb.csproj b/DeepDrftWeb/DeepDrftWeb.csproj index 8db9af4..e9fa3c2 100644 --- a/DeepDrftWeb/DeepDrftWeb.csproj +++ b/DeepDrftWeb/DeepDrftWeb.csproj @@ -8,17 +8,17 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/DeepDrftWeb/Startup.cs b/DeepDrftWeb/Startup.cs index f762135..c72c28c 100644 --- a/DeepDrftWeb/Startup.cs +++ b/DeepDrftWeb/Startup.cs @@ -11,7 +11,7 @@ public static class Startup { // Add Entity Framework services builder.Services.AddDbContext(options => - options.UseSqlite(builder.Configuration.GetConnectionString("DefaultConnection"))); + options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection"))); // Add Server Prerendering Theming Support // DarkModeSettings is registered in DeepDrftWeb.Client.Startup.ConfigureDomainServices diff --git a/DeepDrftWeb/appsettings.json b/DeepDrftWeb/appsettings.json index cb978c1..7262041 100644 --- a/DeepDrftWeb/appsettings.json +++ b/DeepDrftWeb/appsettings.json @@ -7,7 +7,7 @@ }, "AllowedHosts": "*", "ConnectionStrings": { - "DefaultConnection": "Data Source=../Database/deepdrft.db" + "DefaultConnection": "Host=localhost;Database=deepdrft_dev;Username=postgres;Password=postgres" }, "ApiUrls": { "ContentApi": "http://localhost:12777/" diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..9f7d2ac --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,14 @@ +services: + postgres: + image: postgres:17 + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: deepdrft_dev + ports: + - "5432:5432" + volumes: + - deepdrft_postgres:/var/lib/postgresql/data + +volumes: + deepdrft_postgres: