Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,15 @@ indent_size = 2
indent_size = 2
generated_code = true

# Preserve immutable generated migrations with their original byte encoding.
[src/Melodee.Common/Migrations/ArtistSearchEngine/20260622224939_InitialArtistSearchEngineSchema.cs]
charset = utf-8-bom
insert_final_newline = false

[src/Melodee.Common/Migrations/ArtistSearchEngine/20260622225002_SyncMusicBrainzUuidColumns.cs]
charset = utf-8-bom
insert_final_newline = false

# JSON files
[*.{json,json5}]
indent_size = 2
Expand Down
2 changes: 2 additions & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<Project>
<PropertyGroup>
<AllowMissingPrunePackageData>true</AllowMissingPrunePackageData>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);CS1591</NoWarn>
</PropertyGroup>
</Project>
57 changes: 29 additions & 28 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@
<PackageVersion Include="Crc32.NET" Version="1.2.0" />
<PackageVersion Include="CsvHelper" Version="33.1.0" />
<PackageVersion Include="Dapper" Version="2.1.79" />
<PackageVersion Include="DecentDB.AdoNet" Version="2.13.1" />
<PackageVersion Include="DecentDB.EntityFrameworkCore" Version="2.13.1" />
<PackageVersion Include="DecentDB.EntityFrameworkCore.NodaTime" Version="2.13.1" />
<PackageVersion Include="DecentDB.AdoNet" Version="2.16.1" />
<PackageVersion Include="DecentDB.EntityFrameworkCore" Version="2.16.1" />
<PackageVersion Include="DecentDB.EntityFrameworkCore.NodaTime" Version="2.16.1" />
<PackageVersion Include="Fastenshtein" Version="1.0.12" />
<PackageVersion Include="FFMpegCore" Version="5.4.0" />
<PackageVersion Include="FluentAssertions" Version="8.10.0" />
Expand All @@ -30,13 +30,13 @@
<PackageVersion Include="HtmlAgilityPack" Version="1.12.4" />
<PackageVersion Include="HtmlSanitizer" Version="9.1.923-beta" />
<PackageVersion Include="IdGen" Version="3.0.7" />
<PackageVersion Include="JetBrains.Annotations" Version="2025.2.4" />
<PackageVersion Include="Jint" Version="4.10.0" />
<PackageVersion Include="JetBrains.Annotations" Version="2026.2.0" />
<PackageVersion Include="Jint" Version="4.11.0" />
<PackageVersion Include="K4os.Hash.xxHash" Version="1.0.8" />
<PackageVersion Include="LiteDB" Version="5.0.21" />
<PackageVersion Include="Mapster" Version="10.0.8" />
<PackageVersion Include="Markdig" Version="1.3.0" />
<PackageVersion Include="MessagePack" Version="3.1.7" />
<PackageVersion Include="Mapster" Version="10.0.10" />
<PackageVersion Include="Markdig" Version="1.3.2" />
<PackageVersion Include="MessagePack" Version="3.1.8" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.9" />
<PackageVersion Include="Microsoft.AspNetCore.Components.Authorization" Version="10.0.9" />
<PackageVersion Include="Microsoft.AspNetCore.Http" Version="2.3.11" />
Expand Down Expand Up @@ -66,28 +66,29 @@
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.9" />
<PackageVersion Include="Microsoft.IdentityModel.Tokens" Version="8.19.1" />
<PackageVersion Include="Microsoft.Net.Http.Headers" Version="10.0.9" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.6.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.7.0" />
<PackageVersion Include="Microsoft.OpenApi" Version="2.7.5" />
<PackageVersion Include="MailKit" Version="4.17.0" />
<PackageVersion Include="MimeKit" Version="4.17.0" />
<PackageVersion Include="Moq" Version="4.20.72" />
<PackageVersion Include="NBomber" Version="6.4.1" />
<PackageVersion Include="NodaTime" Version="3.3.2" />
<PackageVersion Include="NodaTime.Testing" Version="3.3.2" />
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.2" />
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL.NodaTime" Version="10.0.2" />
<PackageVersion Include="NBomber" Version="6.5.0" />
<PackageVersion Include="NodaTime" Version="3.3.3" />
<PackageVersion Include="NodaTime.Testing" Version="3.3.3" />
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.3" />
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL.NodaTime" Version="10.0.3" />
<PackageVersion Include="PSC.Blazor.Components.MarkdownEditor" Version="10.0.9" />
<PackageVersion Include="Polly" Version="8.7.0" />
<PackageVersion Include="Polly.Core" Version="8.7.0" />
<PackageVersion Include="Quartz.AspNetCore" Version="3.18.1" />
<PackageVersion Include="Quartz.Extensions.DependencyInjection" Version="3.18.1" />
<PackageVersion Include="Quartz.Jobs" Version="3.18.1" />
<PackageVersion Include="Quartz.Plugins.TimeZoneConverter" Version="3.18.1" />
<PackageVersion Include="Quartz.Serialization.SystemTextJson" Version="3.18.1" />
<PackageVersion Include="Radzen.Blazor" Version="10.4.9" />
<PackageVersion Include="Quartz.AspNetCore" Version="3.18.2" />
<PackageVersion Include="Quartz.Extensions.DependencyInjection" Version="3.18.2" />
<PackageVersion Include="Quartz.Jobs" Version="3.18.2" />
<PackageVersion Include="Quartz.Plugins.TimeZoneConverter" Version="3.18.2" />
<PackageVersion Include="Quartz.Serialization.SystemTextJson" Version="3.18.2" />
<PackageVersion Include="Radzen.Blazor" Version="11.1.3" />
<PackageVersion Include="Rebus" Version="8.9.2" />
<PackageVersion Include="Rebus.ServiceProvider" Version="10.7.2" />
<PackageVersion Include="Scalar.AspNetCore" Version="2.16.3" />
<PackageVersion Include="Serilog" Version="4.3.1" />
<PackageVersion Include="Scalar.AspNetCore" Version="2.16.11" />
<PackageVersion Include="Serilog" Version="4.4.0" />
<PackageVersion Include="Serilog.AspNetCore" Version="10.0.0" />
<PackageVersion Include="Serilog.Formatting.Compact" Version="3.0.0" />
<PackageVersion Include="Serilog.Settings.AppSettings" Version="3.0.0" />
Expand All @@ -96,18 +97,18 @@
<PackageVersion Include="Serilog.Sinks.File" Version="7.0.0" />
<PackageVersion Include="SerilogTimings" Version="3.1.0" />
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
<PackageVersion Include="SkiaSharp" Version="3.119.4" />
<PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="3.119.2" />
<PackageVersion Include="SkiaSharp" Version="4.150.0" />
<PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="4.150.0" />
<PackageVersion Include="SmartFormat" Version="3.6.1" />
<PackageVersion Include="Spectre.Console" Version="0.57.0" />
<PackageVersion Include="Spectre.Console" Version="0.57.2" />
<PackageVersion Include="Spectre.Console.Cli" Version="0.55.0" />
<PackageVersion Include="Spectre.Console.Json" Version="0.57.0" />
<PackageVersion Include="Spectre.Console.Testing" Version="0.57.0" />
<PackageVersion Include="Spectre.Console.Json" Version="0.57.2" />
<PackageVersion Include="Spectre.Console.Testing" Version="0.57.2" />
<PackageVersion Include="SpotifyAPI.Web" Version="7.4.2" />
<PackageVersion Include="Sqids" Version="3.2.1" />
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="8.19.1" />
<PackageVersion Include="System.IO.Hashing" Version="10.0.9" />
<PackageVersion Include="System.Linq.Dynamic.Core" Version="1.7.2" />
<PackageVersion Include="System.Linq.Dynamic.Core" Version="1.7.3" />
<PackageVersion Include="System.Reactive" Version="6.1.0" />
<PackageVersion Include="System.Resources.Extensions" Version="10.0.7" />
<PackageVersion Include="System.ServiceModel.Syndication" Version="10.0.9" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<!-- markdownlint-disable-file -->
# Release Changes: EF Core Migrations for Artist Search Engine

**Related Plan**: EF Core Migration Implementation for ArtistSearchEngineServiceDbContext
**Implementation Date**: 2026-06-22

## Summary

Migrated the Artist Search Engine DecentDB database from using `EnsureCreatedAsync()` (which only creates schema on first run) to proper EF Core migrations via `MigrateAsync()`. This enables schema versioning, proper migration tracking, and eliminates hand-rolled idempotent DDL blocks that were maintained manually in `ArtistSearchEngineService.cs`.

## Changes

### Added

- `src/Melodee.Common/Data/ArtistSearchEngineServiceDbContextFactory.cs` - Design-time DbContext factory for `dotnet ef` tooling to generate migrations
- `src/Melodee.Common/Migrations.ArtistSearchEngine/ArtistSearchEngine/20260622141948_InitialArtistSearchEngineSchema.cs` - Baseline migration with raw SQL matching the existing production schema exactly
- `src/Melodee.Common/Migrations/ArtistSearchEngine/20260622141948_InitialArtistSearchEngineSchema.Designer.cs` - EF Core migration metadata/snapshot
- `tests/Melodee.Tests.Common/Data/ArtistSearchEngineServiceDbContextMigrationTests.cs` - 3 comprehensive tests validating migration behavior on fresh files, existing schema, and production schema copy

### Modified

- `src/Melodee.Blazor/Program.cs` - Added `MigrationsAssembly("Melodee.Common")` to DecentDB options for ArtistSearchEngineServiceDbContext so EF Core finds the dedicated migration folder
- `src/Melodee.Common/Services/SearchEngines/ArtistSearchEngineService.cs` - Replaced `EnsureCreatedAsync()` + 3 hand-rolled DDL blocks (`EnsureHousekeepingIndexesAsync`, `EnsureLocalArtistAliasLookupAsync`, `BackfillLocalArtistAliasLookupAsync`) with single `MigrateAsync()` call; kept `BackfillLocalArtistAliasLookupAsync` as a data-seed step
- `src/Melodee.Blazor/Services/DoctorService.cs` - Simplified `ProbeArtistSearchDatabaseAsync` to just verify connectivity without running DDL
- `src/Melodee.Cli/Command/DoctorCommand.cs` - Changed new database creation from `EnsureCreatedAsync()` to `MigrateAsync()`

### Removed

- Hand-rolled `EnsureHousekeepingIndexesAsync` method (index creation now in migration)
- Hand-rolled `EnsureLocalArtistAliasLookupAsync` method (table + index creation now in migration)
- Raw `CREATE INDEX IF NOT EXISTS` and `CREATE TABLE IF NOT EXISTS` blocks from startup path

## Release Summary

**Total Files Affected**: 8

### Files Created (4)

- `src/Melodee.Common/Data/ArtistSearchEngineServiceDbContextFactory.cs` - Design-time factory for migration generation
- `src/Melodee.Common/Migrations/ArtistSearchEngine/20260622141948_InitialArtistSearchEngineSchema.cs` - Baseline migration with idempotent raw SQL matching production DecentDB schema
- `src/Melodee.Common/Migrations/ArtistSearchEngine/20260622141948_InitialArtistSearchEngineSchema.Designer.cs` - Migration metadata
- `tests/Melodee.Tests.Common/Data/ArtistSearchEngineServiceDbContextMigrationTests.cs` - Migration tests

### Files Modified (4)

- `src/Melodee.Blazor/Program.cs` - MigrationsAssembly configuration
- `src/Melodee.Common/Services/SearchEngines/ArtistSearchEngineService.cs` - Switched to MigrateAsync, removed hand-rolled DDL
- `src/Melodee.Blazor/Services/DoctorService.cs` - Simplified health probe
- `src/Melodee.Cli/Command/DoctorCommand.cs` - Switched to MigrateAsync for new database creation

### Dependencies & Infrastructure

- **New Dependencies**: None (uses existing `DecentDB.EntityFrameworkCore` and `DecentDB.EntityFrameworkCore.NodaTime`)
- **Updated Dependencies**: None
- **Infrastructure Changes**: Created dedicated migration folder `src/Melodee.Common/Migrations.ArtistSearchEngine/` for isolated migration tracking
- **Configuration Updates**: `MigrationsAssembly("Melodee.Common")` on DbContext options to locate migration history table

### Deployment Notes

1. **Production file compatibility**: The baseline migration uses raw SQL that exactly matches the existing production `.ddb` schema (verified via DecentDB CLI dump). Running `MigrateAsync()` against the existing file is a no-op — it inserts the `__EFMigrationsHistory` row and leaves all tables/indexes/data intact.

2. **New environments**: Fresh `.ddb` files will have the full schema created by the migration's `CREATE TABLE IF NOT EXISTS` / `CREATE INDEX IF NOT EXISTS` statements.

3. **Future schema changes**: New schema modifications should be done by modifying the entity models, then running `dotnet ef migrations add <Name> --project src/Melodee.Common --startup-project src/Melodee.Blazor --context ArtistSearchEngineServiceDbContext --output-dir Migrations.ArtistSearchEngine` to generate proper incremental migrations.

4. **Test fixture**: The production schema test requires setting `MELODEE_TEST_ARTIST_SEARCH_ENGINE_DDB` environment variable to a DecentDB file matching the production schema (not committed due to size).

## Local Development Baselining Fix

**Problem**: Local `.ddb` files created with the old `EnsureCreatedAsync()` path lacked the `__EFMigrationsHistory` table. When `MigrateAsync()` ran, it tried to read the history table (failed), then compared the model snapshot to the database and reported "pending changes" — even though the schema matched.

**Root cause**: The model snapshot (in `Designer.cs`) had `BOOLEAN` for `IsLocked` while the database had `INT64` (from the old `EnsureCreatedAsync()`). EF Core's model validator flagged this as a schema mismatch.

**Fix applied**:
1. Added explicit value converter for `Artist.IsLocked` in `ArtistSearchEngineServiceDbContext.OnModelCreating()` mapping `bool?` → `int?` (0/1) stored as `INTEGER`
2. Updated the baseline migration's `Designer.cs` model snapshot to match the database: `IsLocked` now uses `INTEGER` with the value converter (instead of `BOOLEAN`)
3. **No additional migration was created** — the baseline migration's model snapshot was manually corrected to match the physical database schema, so EF Core's model validator sees zero differences

**Result**: Existing local `.ddb` files with historical data work unchanged. `MigrateAsync()` sees the baseline history row, finds zero pending migrations, and proceeds without errors.
2 changes: 1 addition & 1 deletion docs/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2.1.4
2.2.0
67 changes: 67 additions & 0 deletions docs/pages/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,73 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [2.2.0] - 2026-07-11

### Added

- Admin health checks now recognize DecentDB error 8 and automatically open a
migration dialog with the matching prebuilt release, configured source and
destination paths, copyable `decentdb-migrate` commands, verification steps,
safe replacement scripts, and links to the official DecentDB guidance.
- Media Artist export/import: export all search-engine artist data (artists, albums,
aliases) as JSON via the `/admin/mediaartists` page; import JSON exports with optional
overwrite and full preview of artist/album/alias counts.
- ArtistSearch database auto-creation in Blazor Doctor: when the `.ddb` file is missing
but the parent directory exists, the health check now creates it via EF Core migrations
instead of reporting an error.

### Changed

- Replaced the hand-rolled raw-SQL ArtistSearch initial migration with a proper
model-driven EF Core migration that uses `CREATE TABLE IF NOT EXISTS` for idempotent
application on both fresh and existing databases.
- Added a second `SyncMusicBrainzUuidColumns` migration as a no-op to satisfy EF Core's
migration chain. DecentDB reports `Guid`/`byte[]` columns as `UUID` in the EF model but
stores them as `BLOB`, and `ALTER COLUMN TYPE` to `UUID` is unsupported by DecentDB
(only `INT64`, `FLOAT64`, `TEXT`, `BOOL`). The no-op migration records itself as applied
without attempting the unsupported `ALTER`, keeping `MigrateAsync` stable.
- Updated the DecentDB providers to `2.16.1`, SkiaSharp managed and Linux native
assets to `4.150.0`, Radzen to `11.1.3`, and related runtime, UI, test, and
tooling dependencies.
- Chart editing, user, user-group, and podcast detail routes now bind public
GUID API keys directly instead of internal numeric IDs or custom string
parsing.
- Enabled solution-wide XML documentation output so build-time unused-using
analysis runs consistently, and corrected malformed API documentation and
formatter configuration.

### Fixed

- Resolved EF Core "pending model changes" error for `ArtistSearchEngineServiceDbContext`
by regenerating the model snapshot to match the `IsLocked` `INTEGER` column type
configured in the DbContext.
- Resolved DecentDB `ALTER COLUMN TYPE supports only INT64, FLOAT64, TEXT, and BOOL` crash
during ArtistSearch migration by making the `SyncMusicBrainzUuidColumns` migration a no-op.
- ArtistSearch database is now auto-created on first use when the `.ddb` file is absent,
eliminating the "database is empty or not initialized" error that previously required
manual intervention.
- Paged ArtistSearch queries now retrieve the requested page before counting
results, preventing DecentDB positional parameters from carrying across
commands and causing missing-parameter failures.
- Chart edit navigation now uses the chart API key, and missing chart album
matching is accent-insensitive.
- Aligned the SkiaSharp managed and Linux native packages and moved image
drawing to the supported sampling API, preventing native `119.0` and managed
`150.0` incompatibility failures during image processing.
- Solution Debug and CI analyzer builds now complete without dependency,
obsolete API, or XML documentation warnings.
- Version bump automation now promotes release notes without moving the fresh
Unreleased heading above the changelog's Jekyll front matter.
- ArtistSearch initialization now uses migrations for relational databases and
schema creation for non-relational providers, preventing OpenSubsonic
`getTopSongs` failures in in-memory hosts.

### Security

- Pinned `Microsoft.OpenApi` to patched version `2.7.5`, preventing crafted
circular schema references from terminating the process during OpenAPI
document parsing.

## [2.1.4] - 2026-06-16

### Added
Expand Down
Loading
Loading