Merge cms-w1-t1-postgres: SQLite → PostgreSQL migration

This commit is contained in:
Daniel Harvey
2026-05-17 23:31:01 -04:00
16 changed files with 128 additions and 103 deletions
+1
View File
@@ -297,6 +297,7 @@ __pycache__/
# Environment Secrets # Environment Secrets
**/environment **/environment
**/appsettings.Development.json
# local sqlite files # local sqlite files
**/deepdrft.db* **/deepdrft.db*
+9 -8
View File
@@ -8,14 +8,15 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.1" /> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.1" /> <!-- Npgsql 10.0.1 requires Microsoft.EntityFrameworkCore >= 10.0.4; keep in sync -->
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.1" /> <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="10.0.1" /> <PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.4" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.1" /> <PackageReference Include="Microsoft.Extensions.Configuration" Version="10.0.4" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.1" /> <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.4" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.1" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.4" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="10.0.1" /> <PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.4" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="10.0.4" />
<PackageReference Include="Terminal.Gui" Version="1.19.0" /> <PackageReference Include="Terminal.Gui" Version="1.19.0" />
</ItemGroup> </ItemGroup>
+1 -1
View File
@@ -22,7 +22,7 @@ builder.Services.AddLogging(configure => configure.AddConsole());
// Add database context // Add database context
builder.Services.AddDbContext<DeepDrftContext>(options => builder.Services.AddDbContext<DeepDrftContext>(options =>
options.UseSqlite(cliSettings.ConnectionString)); options.UseNpgsql(cliSettings.ConnectionString));
// Add FileDatabase // Add FileDatabase
builder.Services.AddSingleton<FileDatabase>(provider => builder.Services.AddSingleton<FileDatabase>(provider =>
+1 -1
View File
@@ -7,7 +7,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="C:\lib\NetBlocks\NetBlocks.csproj" /> <PackageReference Include="Cerebellum.NetBlocks" Version="10.1.1" />
</ItemGroup> </ItemGroup>
</Project> </Project>
@@ -7,8 +7,15 @@ public class DeepDrftContextFactory : IDesignTimeDbContextFactory<DeepDrftContex
{ {
public DeepDrftContext CreateDbContext(string[] args) public DeepDrftContext CreateDbContext(string[] args)
{ {
// For 'dotnet ef' commands, set ConnectionStrings__DefaultConnection in your environment.
// Example: export ConnectionStrings__DefaultConnection="Host=localhost;Database=deepdrft_dev;Username=postgres;Password=yourpassword"
var connectionString = Environment.GetEnvironmentVariable("ConnectionStrings__DefaultConnection")
?? throw new InvalidOperationException(
"Set ConnectionStrings__DefaultConnection environment variable to run dotnet ef commands. " +
"Example: Host=localhost;Database=deepdrft_dev;Username=postgres;Password=yourpassword");
var optionsBuilder = new DbContextOptionsBuilder<DeepDrftContext>(); var optionsBuilder = new DbContextOptionsBuilder<DeepDrftContext>();
optionsBuilder.UseSqlite("Data Source=../Database/deepdrft.db"); optionsBuilder.UseNpgsql(connectionString);
return new DeepDrftContext(optionsBuilder.Options); return new DeepDrftContext(optionsBuilder.Options);
} }
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net10.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
@@ -7,12 +7,17 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.1" /> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.1" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.4">
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.1">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<!-- Npgsql 10.0.1 requires Microsoft.EntityFrameworkCore >= 10.0.4; keep in sync -->
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.1" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -1,41 +0,0 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace DeepDrftWeb.Migrations
{
/// <inheritdoc />
public partial class Initial : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "track",
columns: table => new
{
id = table.Column<long>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
entry_key = table.Column<string>(type: "TEXT", maxLength: 100, nullable: false),
track_name = table.Column<string>(type: "TEXT", maxLength: 200, nullable: false),
artist = table.Column<string>(type: "TEXT", maxLength: 200, nullable: false),
album = table.Column<string>(type: "TEXT", maxLength: 200, nullable: true),
genre = table.Column<string>(type: "TEXT", maxLength: 100, nullable: true),
release_date = table.Column<DateOnly>(type: "TEXT", nullable: true),
image_path = table.Column<string>(type: "TEXT", maxLength: 500, nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_track", x => x.id);
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "track");
}
}
}
@@ -5,63 +5,70 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable #nullable disable
namespace DeepDrftWeb.Migrations namespace DeepDrftWeb.Migrations
{ {
[DbContext(typeof(DeepDrftContext))] [DbContext(typeof(DeepDrftContext))]
[Migration("20250904233927_Initial")] [Migration("20260518025102_Initial")]
partial class Initial partial class Initial
{ {
/// <inheritdoc /> /// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder) protected override void BuildTargetModel(ModelBuilder modelBuilder)
{ {
#pragma warning disable 612, 618 #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 => modelBuilder.Entity("DeepDrftModels.Entities.TrackEntity", b =>
{ {
b.Property<long>("Id") b.Property<long>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("INTEGER") .HasColumnType("bigint")
.HasColumnName("id"); .HasColumnName("id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
b.Property<string>("Album") b.Property<string>("Album")
.HasMaxLength(200) .HasMaxLength(200)
.HasColumnType("TEXT") .HasColumnType("character varying(200)")
.HasColumnName("album"); .HasColumnName("album");
b.Property<string>("Artist") b.Property<string>("Artist")
.IsRequired() .IsRequired()
.HasMaxLength(200) .HasMaxLength(200)
.HasColumnType("TEXT") .HasColumnType("character varying(200)")
.HasColumnName("artist"); .HasColumnName("artist");
b.Property<string>("EntryKey") b.Property<string>("EntryKey")
.IsRequired() .IsRequired()
.HasMaxLength(100) .HasMaxLength(100)
.HasColumnType("TEXT") .HasColumnType("character varying(100)")
.HasColumnName("entry_key"); .HasColumnName("entry_key");
b.Property<string>("Genre") b.Property<string>("Genre")
.HasMaxLength(100) .HasMaxLength(100)
.HasColumnType("TEXT") .HasColumnType("character varying(100)")
.HasColumnName("genre"); .HasColumnName("genre");
b.Property<string>("ImagePath") b.Property<string>("ImagePath")
.HasMaxLength(500) .HasMaxLength(500)
.HasColumnType("TEXT") .HasColumnType("character varying(500)")
.HasColumnName("image_path"); .HasColumnName("image_path");
b.Property<DateOnly?>("ReleaseDate") b.Property<DateOnly?>("ReleaseDate")
.HasColumnType("TEXT") .HasColumnType("date")
.HasColumnName("release_date"); .HasColumnName("release_date");
b.Property<string>("TrackName") b.Property<string>("TrackName")
.IsRequired() .IsRequired()
.HasMaxLength(200) .HasMaxLength(200)
.HasColumnType("TEXT") .HasColumnType("character varying(200)")
.HasColumnName("track_name"); .HasColumnName("track_name");
b.HasKey("Id"); b.HasKey("Id");
@@ -0,0 +1,42 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace DeepDrftWeb.Migrations
{
/// <inheritdoc />
public partial class Initial : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "track",
columns: table => new
{
id = table.Column<long>(type: "bigint", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
entry_key = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: false),
track_name = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: false),
artist = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: false),
album = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: true),
genre = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: true),
release_date = table.Column<DateOnly>(type: "date", nullable: true),
image_path = table.Column<string>(type: "character varying(500)", maxLength: 500, nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_track", x => x.id);
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "track");
}
}
}
@@ -4,6 +4,7 @@ using DeepDrftWeb.Services.Data;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable #nullable disable
@@ -15,50 +16,56 @@ namespace DeepDrftWeb.Migrations
protected override void BuildModel(ModelBuilder modelBuilder) protected override void BuildModel(ModelBuilder modelBuilder)
{ {
#pragma warning disable 612, 618 #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 => modelBuilder.Entity("DeepDrftModels.Entities.TrackEntity", b =>
{ {
b.Property<long>("Id") b.Property<long>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("INTEGER") .HasColumnType("bigint")
.HasColumnName("id"); .HasColumnName("id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
b.Property<string>("Album") b.Property<string>("Album")
.HasMaxLength(200) .HasMaxLength(200)
.HasColumnType("TEXT") .HasColumnType("character varying(200)")
.HasColumnName("album"); .HasColumnName("album");
b.Property<string>("Artist") b.Property<string>("Artist")
.IsRequired() .IsRequired()
.HasMaxLength(200) .HasMaxLength(200)
.HasColumnType("TEXT") .HasColumnType("character varying(200)")
.HasColumnName("artist"); .HasColumnName("artist");
b.Property<string>("EntryKey") b.Property<string>("EntryKey")
.IsRequired() .IsRequired()
.HasMaxLength(100) .HasMaxLength(100)
.HasColumnType("TEXT") .HasColumnType("character varying(100)")
.HasColumnName("entry_key"); .HasColumnName("entry_key");
b.Property<string>("Genre") b.Property<string>("Genre")
.HasMaxLength(100) .HasMaxLength(100)
.HasColumnType("TEXT") .HasColumnType("character varying(100)")
.HasColumnName("genre"); .HasColumnName("genre");
b.Property<string>("ImagePath") b.Property<string>("ImagePath")
.HasMaxLength(500) .HasMaxLength(500)
.HasColumnType("TEXT") .HasColumnType("character varying(500)")
.HasColumnName("image_path"); .HasColumnName("image_path");
b.Property<DateOnly?>("ReleaseDate") b.Property<DateOnly?>("ReleaseDate")
.HasColumnType("TEXT") .HasColumnType("date")
.HasColumnName("release_date"); .HasColumnName("release_date");
b.Property<string>("TrackName") b.Property<string>("TrackName")
.IsRequired() .IsRequired()
.HasMaxLength(200) .HasMaxLength(200)
.HasColumnType("TEXT") .HasColumnType("character varying(200)")
.HasColumnName("track_name"); .HasColumnName("track_name");
b.HasKey("Id"); b.HasKey("Id");
@@ -27,7 +27,7 @@ public class TrackRepository
public async Task<PagedResult<TrackEntity>> GetPage(PagingParameters<TrackEntity> pageParameters) public async Task<PagedResult<TrackEntity>> GetPage(PagingParameters<TrackEntity> pageParameters)
{ {
// Two separate queries with no transaction: count and page can be momentarily inconsistent // 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. // If filtering is added, the count query must be updated to apply the same filter.
var count = await _db.Tracks.CountAsync(); var count = await _db.Tracks.CountAsync();
+6 -5
View File
@@ -8,17 +8,18 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.1" /> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.1"> <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.4">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="10.0.1" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="10.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.1" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.4">
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.1">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<!-- Npgsql 10.0.1 requires Microsoft.EntityFrameworkCore >= 10.0.4; keep in sync -->
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.1" />
<ProjectReference Include="..\DeepDrftModels\DeepDrftModels.csproj" /> <ProjectReference Include="..\DeepDrftModels\DeepDrftModels.csproj" />
<ProjectReference Include="..\DeepDrftWeb.Client\DeepDrftWeb.Client.csproj" /> <ProjectReference Include="..\DeepDrftWeb.Client\DeepDrftWeb.Client.csproj" />
<ProjectReference Include="..\DeepDrftWeb.Services\DeepDrftWeb.Services.csproj" /> <ProjectReference Include="..\DeepDrftWeb.Services\DeepDrftWeb.Services.csproj" />
+1 -1
View File
@@ -11,7 +11,7 @@ public static class Startup
{ {
// Add Entity Framework services // Add Entity Framework services
builder.Services.AddDbContext<DeepDrftContext>(options => builder.Services.AddDbContext<DeepDrftContext>(options =>
options.UseSqlite(builder.Configuration.GetConnectionString("DefaultConnection"))); options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection")));
// Add Server Prerendering Theming Support // Add Server Prerendering Theming Support
// DarkModeSettings is registered in DeepDrftWeb.Client.Startup.ConfigureDomainServices // DarkModeSettings is registered in DeepDrftWeb.Client.Startup.ConfigureDomainServices
-19
View File
@@ -1,19 +0,0 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"DeepDrftWeb.Client.Services.StreamingAudioPlayerService": "Debug"
},
"Console": {
"FormatterName": "simple",
"LogLevel": {
"Default": "Information"
}
}
},
"DetailedErrors": true,
"ApiUrls": {
"ContentApi": "http://localhost:54494/"
}
}
+1 -1
View File
@@ -7,7 +7,7 @@
}, },
"AllowedHosts": "*", "AllowedHosts": "*",
"ConnectionStrings": { "ConnectionStrings": {
"DefaultConnection": "Data Source=../Database/deepdrft.db" "DefaultConnection": "Host=localhost;Database=deepdrft_dev;Username=postgres;Password=REPLACE_IN_ENV"
}, },
"ApiUrls": { "ApiUrls": {
"ContentApi": "http://localhost:12777/" "ContentApi": "http://localhost:12777/"
+14
View File
@@ -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: