Skip to content

Commit d91342e

Browse files
authored
Merge pull request #75 from melodee-project/sph.2026-06-22.01
v2.2.0 PR
2 parents 10c73eb + 17daced commit d91342e

79 files changed

Lines changed: 6292 additions & 306 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.editorconfig

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,15 @@ indent_size = 2
2323
indent_size = 2
2424
generated_code = true
2525

26+
# Preserve immutable generated migrations with their original byte encoding.
27+
[src/Melodee.Common/Migrations/ArtistSearchEngine/20260622224939_InitialArtistSearchEngineSchema.cs]
28+
charset = utf-8-bom
29+
insert_final_newline = false
30+
31+
[src/Melodee.Common/Migrations/ArtistSearchEngine/20260622225002_SyncMusicBrainzUuidColumns.cs]
32+
charset = utf-8-bom
33+
insert_final_newline = false
34+
2635
# JSON files
2736
[*.{json,json5}]
2837
indent_size = 2

Directory.Build.props

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
<Project>
22
<PropertyGroup>
33
<AllowMissingPrunePackageData>true</AllowMissingPrunePackageData>
4+
<GenerateDocumentationFile>true</GenerateDocumentationFile>
5+
<NoWarn>$(NoWarn);CS1591</NoWarn>
46
</PropertyGroup>
57
</Project>

Directory.Packages.props

Lines changed: 29 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@
1717
<PackageVersion Include="Crc32.NET" Version="1.2.0" />
1818
<PackageVersion Include="CsvHelper" Version="33.1.0" />
1919
<PackageVersion Include="Dapper" Version="2.1.79" />
20-
<PackageVersion Include="DecentDB.AdoNet" Version="2.13.1" />
21-
<PackageVersion Include="DecentDB.EntityFrameworkCore" Version="2.13.1" />
22-
<PackageVersion Include="DecentDB.EntityFrameworkCore.NodaTime" Version="2.13.1" />
20+
<PackageVersion Include="DecentDB.AdoNet" Version="2.16.1" />
21+
<PackageVersion Include="DecentDB.EntityFrameworkCore" Version="2.16.1" />
22+
<PackageVersion Include="DecentDB.EntityFrameworkCore.NodaTime" Version="2.16.1" />
2323
<PackageVersion Include="Fastenshtein" Version="1.0.12" />
2424
<PackageVersion Include="FFMpegCore" Version="5.4.0" />
2525
<PackageVersion Include="FluentAssertions" Version="8.10.0" />
@@ -30,13 +30,13 @@
3030
<PackageVersion Include="HtmlAgilityPack" Version="1.12.4" />
3131
<PackageVersion Include="HtmlSanitizer" Version="9.1.923-beta" />
3232
<PackageVersion Include="IdGen" Version="3.0.7" />
33-
<PackageVersion Include="JetBrains.Annotations" Version="2025.2.4" />
34-
<PackageVersion Include="Jint" Version="4.10.0" />
33+
<PackageVersion Include="JetBrains.Annotations" Version="2026.2.0" />
34+
<PackageVersion Include="Jint" Version="4.11.0" />
3535
<PackageVersion Include="K4os.Hash.xxHash" Version="1.0.8" />
3636
<PackageVersion Include="LiteDB" Version="5.0.21" />
37-
<PackageVersion Include="Mapster" Version="10.0.8" />
38-
<PackageVersion Include="Markdig" Version="1.3.0" />
39-
<PackageVersion Include="MessagePack" Version="3.1.7" />
37+
<PackageVersion Include="Mapster" Version="10.0.10" />
38+
<PackageVersion Include="Markdig" Version="1.3.2" />
39+
<PackageVersion Include="MessagePack" Version="3.1.8" />
4040
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.9" />
4141
<PackageVersion Include="Microsoft.AspNetCore.Components.Authorization" Version="10.0.9" />
4242
<PackageVersion Include="Microsoft.AspNetCore.Http" Version="2.3.11" />
@@ -66,28 +66,29 @@
6666
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.9" />
6767
<PackageVersion Include="Microsoft.IdentityModel.Tokens" Version="8.19.1" />
6868
<PackageVersion Include="Microsoft.Net.Http.Headers" Version="10.0.9" />
69-
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.6.0" />
69+
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.7.0" />
70+
<PackageVersion Include="Microsoft.OpenApi" Version="2.7.5" />
7071
<PackageVersion Include="MailKit" Version="4.17.0" />
7172
<PackageVersion Include="MimeKit" Version="4.17.0" />
7273
<PackageVersion Include="Moq" Version="4.20.72" />
73-
<PackageVersion Include="NBomber" Version="6.4.1" />
74-
<PackageVersion Include="NodaTime" Version="3.3.2" />
75-
<PackageVersion Include="NodaTime.Testing" Version="3.3.2" />
76-
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.2" />
77-
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL.NodaTime" Version="10.0.2" />
74+
<PackageVersion Include="NBomber" Version="6.5.0" />
75+
<PackageVersion Include="NodaTime" Version="3.3.3" />
76+
<PackageVersion Include="NodaTime.Testing" Version="3.3.3" />
77+
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.3" />
78+
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL.NodaTime" Version="10.0.3" />
7879
<PackageVersion Include="PSC.Blazor.Components.MarkdownEditor" Version="10.0.9" />
7980
<PackageVersion Include="Polly" Version="8.7.0" />
8081
<PackageVersion Include="Polly.Core" Version="8.7.0" />
81-
<PackageVersion Include="Quartz.AspNetCore" Version="3.18.1" />
82-
<PackageVersion Include="Quartz.Extensions.DependencyInjection" Version="3.18.1" />
83-
<PackageVersion Include="Quartz.Jobs" Version="3.18.1" />
84-
<PackageVersion Include="Quartz.Plugins.TimeZoneConverter" Version="3.18.1" />
85-
<PackageVersion Include="Quartz.Serialization.SystemTextJson" Version="3.18.1" />
86-
<PackageVersion Include="Radzen.Blazor" Version="10.4.9" />
82+
<PackageVersion Include="Quartz.AspNetCore" Version="3.18.2" />
83+
<PackageVersion Include="Quartz.Extensions.DependencyInjection" Version="3.18.2" />
84+
<PackageVersion Include="Quartz.Jobs" Version="3.18.2" />
85+
<PackageVersion Include="Quartz.Plugins.TimeZoneConverter" Version="3.18.2" />
86+
<PackageVersion Include="Quartz.Serialization.SystemTextJson" Version="3.18.2" />
87+
<PackageVersion Include="Radzen.Blazor" Version="11.1.3" />
8788
<PackageVersion Include="Rebus" Version="8.9.2" />
8889
<PackageVersion Include="Rebus.ServiceProvider" Version="10.7.2" />
89-
<PackageVersion Include="Scalar.AspNetCore" Version="2.16.3" />
90-
<PackageVersion Include="Serilog" Version="4.3.1" />
90+
<PackageVersion Include="Scalar.AspNetCore" Version="2.16.11" />
91+
<PackageVersion Include="Serilog" Version="4.4.0" />
9192
<PackageVersion Include="Serilog.AspNetCore" Version="10.0.0" />
9293
<PackageVersion Include="Serilog.Formatting.Compact" Version="3.0.0" />
9394
<PackageVersion Include="Serilog.Settings.AppSettings" Version="3.0.0" />
@@ -96,18 +97,18 @@
9697
<PackageVersion Include="Serilog.Sinks.File" Version="7.0.0" />
9798
<PackageVersion Include="SerilogTimings" Version="3.1.0" />
9899
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
99-
<PackageVersion Include="SkiaSharp" Version="3.119.4" />
100-
<PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="3.119.2" />
100+
<PackageVersion Include="SkiaSharp" Version="4.150.0" />
101+
<PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="4.150.0" />
101102
<PackageVersion Include="SmartFormat" Version="3.6.1" />
102-
<PackageVersion Include="Spectre.Console" Version="0.57.0" />
103+
<PackageVersion Include="Spectre.Console" Version="0.57.2" />
103104
<PackageVersion Include="Spectre.Console.Cli" Version="0.55.0" />
104-
<PackageVersion Include="Spectre.Console.Json" Version="0.57.0" />
105-
<PackageVersion Include="Spectre.Console.Testing" Version="0.57.0" />
105+
<PackageVersion Include="Spectre.Console.Json" Version="0.57.2" />
106+
<PackageVersion Include="Spectre.Console.Testing" Version="0.57.2" />
106107
<PackageVersion Include="SpotifyAPI.Web" Version="7.4.2" />
107108
<PackageVersion Include="Sqids" Version="3.2.1" />
108109
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="8.19.1" />
109110
<PackageVersion Include="System.IO.Hashing" Version="10.0.9" />
110-
<PackageVersion Include="System.Linq.Dynamic.Core" Version="1.7.2" />
111+
<PackageVersion Include="System.Linq.Dynamic.Core" Version="1.7.3" />
111112
<PackageVersion Include="System.Reactive" Version="6.1.0" />
112113
<PackageVersion Include="System.Resources.Extensions" Version="10.0.7" />
113114
<PackageVersion Include="System.ServiceModel.Syndication" Version="10.0.9" />
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<!-- markdownlint-disable-file -->
2+
# Release Changes: EF Core Migrations for Artist Search Engine
3+
4+
**Related Plan**: EF Core Migration Implementation for ArtistSearchEngineServiceDbContext
5+
**Implementation Date**: 2026-06-22
6+
7+
## Summary
8+
9+
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`.
10+
11+
## Changes
12+
13+
### Added
14+
15+
- `src/Melodee.Common/Data/ArtistSearchEngineServiceDbContextFactory.cs` - Design-time DbContext factory for `dotnet ef` tooling to generate migrations
16+
- `src/Melodee.Common/Migrations.ArtistSearchEngine/ArtistSearchEngine/20260622141948_InitialArtistSearchEngineSchema.cs` - Baseline migration with raw SQL matching the existing production schema exactly
17+
- `src/Melodee.Common/Migrations/ArtistSearchEngine/20260622141948_InitialArtistSearchEngineSchema.Designer.cs` - EF Core migration metadata/snapshot
18+
- `tests/Melodee.Tests.Common/Data/ArtistSearchEngineServiceDbContextMigrationTests.cs` - 3 comprehensive tests validating migration behavior on fresh files, existing schema, and production schema copy
19+
20+
### Modified
21+
22+
- `src/Melodee.Blazor/Program.cs` - Added `MigrationsAssembly("Melodee.Common")` to DecentDB options for ArtistSearchEngineServiceDbContext so EF Core finds the dedicated migration folder
23+
- `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
24+
- `src/Melodee.Blazor/Services/DoctorService.cs` - Simplified `ProbeArtistSearchDatabaseAsync` to just verify connectivity without running DDL
25+
- `src/Melodee.Cli/Command/DoctorCommand.cs` - Changed new database creation from `EnsureCreatedAsync()` to `MigrateAsync()`
26+
27+
### Removed
28+
29+
- Hand-rolled `EnsureHousekeepingIndexesAsync` method (index creation now in migration)
30+
- Hand-rolled `EnsureLocalArtistAliasLookupAsync` method (table + index creation now in migration)
31+
- Raw `CREATE INDEX IF NOT EXISTS` and `CREATE TABLE IF NOT EXISTS` blocks from startup path
32+
33+
## Release Summary
34+
35+
**Total Files Affected**: 8
36+
37+
### Files Created (4)
38+
39+
- `src/Melodee.Common/Data/ArtistSearchEngineServiceDbContextFactory.cs` - Design-time factory for migration generation
40+
- `src/Melodee.Common/Migrations/ArtistSearchEngine/20260622141948_InitialArtistSearchEngineSchema.cs` - Baseline migration with idempotent raw SQL matching production DecentDB schema
41+
- `src/Melodee.Common/Migrations/ArtistSearchEngine/20260622141948_InitialArtistSearchEngineSchema.Designer.cs` - Migration metadata
42+
- `tests/Melodee.Tests.Common/Data/ArtistSearchEngineServiceDbContextMigrationTests.cs` - Migration tests
43+
44+
### Files Modified (4)
45+
46+
- `src/Melodee.Blazor/Program.cs` - MigrationsAssembly configuration
47+
- `src/Melodee.Common/Services/SearchEngines/ArtistSearchEngineService.cs` - Switched to MigrateAsync, removed hand-rolled DDL
48+
- `src/Melodee.Blazor/Services/DoctorService.cs` - Simplified health probe
49+
- `src/Melodee.Cli/Command/DoctorCommand.cs` - Switched to MigrateAsync for new database creation
50+
51+
### Dependencies & Infrastructure
52+
53+
- **New Dependencies**: None (uses existing `DecentDB.EntityFrameworkCore` and `DecentDB.EntityFrameworkCore.NodaTime`)
54+
- **Updated Dependencies**: None
55+
- **Infrastructure Changes**: Created dedicated migration folder `src/Melodee.Common/Migrations.ArtistSearchEngine/` for isolated migration tracking
56+
- **Configuration Updates**: `MigrationsAssembly("Melodee.Common")` on DbContext options to locate migration history table
57+
58+
### Deployment Notes
59+
60+
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.
61+
62+
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.
63+
64+
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.
65+
66+
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).
67+
68+
## Local Development Baselining Fix
69+
70+
**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.
71+
72+
**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.
73+
74+
**Fix applied**:
75+
1. Added explicit value converter for `Artist.IsLocked` in `ArtistSearchEngineServiceDbContext.OnModelCreating()` mapping `bool?``int?` (0/1) stored as `INTEGER`
76+
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`)
77+
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
78+
79+
**Result**: Existing local `.ddb` files with historical data work unchanged. `MigrateAsync()` sees the baseline history row, finds zero pending migrations, and proceeds without errors.

docs/VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
2.1.4
1+
2.2.0

docs/pages/changelog.md

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,73 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2323

2424
## [Unreleased]
2525

26+
## [2.2.0] - 2026-07-11
27+
28+
### Added
29+
30+
- Admin health checks now recognize DecentDB error 8 and automatically open a
31+
migration dialog with the matching prebuilt release, configured source and
32+
destination paths, copyable `decentdb-migrate` commands, verification steps,
33+
safe replacement scripts, and links to the official DecentDB guidance.
34+
- Media Artist export/import: export all search-engine artist data (artists, albums,
35+
aliases) as JSON via the `/admin/mediaartists` page; import JSON exports with optional
36+
overwrite and full preview of artist/album/alias counts.
37+
- ArtistSearch database auto-creation in Blazor Doctor: when the `.ddb` file is missing
38+
but the parent directory exists, the health check now creates it via EF Core migrations
39+
instead of reporting an error.
40+
41+
### Changed
42+
43+
- Replaced the hand-rolled raw-SQL ArtistSearch initial migration with a proper
44+
model-driven EF Core migration that uses `CREATE TABLE IF NOT EXISTS` for idempotent
45+
application on both fresh and existing databases.
46+
- Added a second `SyncMusicBrainzUuidColumns` migration as a no-op to satisfy EF Core's
47+
migration chain. DecentDB reports `Guid`/`byte[]` columns as `UUID` in the EF model but
48+
stores them as `BLOB`, and `ALTER COLUMN TYPE` to `UUID` is unsupported by DecentDB
49+
(only `INT64`, `FLOAT64`, `TEXT`, `BOOL`). The no-op migration records itself as applied
50+
without attempting the unsupported `ALTER`, keeping `MigrateAsync` stable.
51+
- Updated the DecentDB providers to `2.16.1`, SkiaSharp managed and Linux native
52+
assets to `4.150.0`, Radzen to `11.1.3`, and related runtime, UI, test, and
53+
tooling dependencies.
54+
- Chart editing, user, user-group, and podcast detail routes now bind public
55+
GUID API keys directly instead of internal numeric IDs or custom string
56+
parsing.
57+
- Enabled solution-wide XML documentation output so build-time unused-using
58+
analysis runs consistently, and corrected malformed API documentation and
59+
formatter configuration.
60+
61+
### Fixed
62+
63+
- Resolved EF Core "pending model changes" error for `ArtistSearchEngineServiceDbContext`
64+
by regenerating the model snapshot to match the `IsLocked` `INTEGER` column type
65+
configured in the DbContext.
66+
- Resolved DecentDB `ALTER COLUMN TYPE supports only INT64, FLOAT64, TEXT, and BOOL` crash
67+
during ArtistSearch migration by making the `SyncMusicBrainzUuidColumns` migration a no-op.
68+
- ArtistSearch database is now auto-created on first use when the `.ddb` file is absent,
69+
eliminating the "database is empty or not initialized" error that previously required
70+
manual intervention.
71+
- Paged ArtistSearch queries now retrieve the requested page before counting
72+
results, preventing DecentDB positional parameters from carrying across
73+
commands and causing missing-parameter failures.
74+
- Chart edit navigation now uses the chart API key, and missing chart album
75+
matching is accent-insensitive.
76+
- Aligned the SkiaSharp managed and Linux native packages and moved image
77+
drawing to the supported sampling API, preventing native `119.0` and managed
78+
`150.0` incompatibility failures during image processing.
79+
- Solution Debug and CI analyzer builds now complete without dependency,
80+
obsolete API, or XML documentation warnings.
81+
- Version bump automation now promotes release notes without moving the fresh
82+
Unreleased heading above the changelog's Jekyll front matter.
83+
- ArtistSearch initialization now uses migrations for relational databases and
84+
schema creation for non-relational providers, preventing OpenSubsonic
85+
`getTopSongs` failures in in-memory hosts.
86+
87+
### Security
88+
89+
- Pinned `Microsoft.OpenApi` to patched version `2.7.5`, preventing crafted
90+
circular schema references from terminating the process during OpenAPI
91+
document parsing.
92+
2693
## [2.1.4] - 2026-06-16
2794

2895
### Added

0 commit comments

Comments
 (0)