Track.Artist was a plain string snapshot with no identity, and
UserHasTrack doubled as both "in my library" and "I own this",
conflating collection membership with administrative ownership.
Introduce Artist/TrackArtist to give artist credits their own
identity (needed so YouTube tracks with multiple credited artists,
and future renames, can be modeled), and Track.OwnerUserId to
represent ownership explicitly (local tracks only, enforced by a
check constraint). CreateTrack now provisions the uploader's Artist
row and links it via TrackArtist; DeleteTrack authorizes against
OwnerUserId instead of any UserHasTrack row.
Artist, ExternalId, OriginalPictureName and OriginalAudioName were
non-nullable with '' sentinels, which forced the unique (Source,
ExternalId) index to rely on a filtered "<> ''" clause. Make them
truly nullable and drop the empty-string sentinels, backfilling
existing rows to NULL. Also fixes a formatting regression
(`track != null&&`) introduced alongside the nullability change.
Playing a YouTube song now creates a lightweight track row (without queueing a download) and writes listening history; saving to a playlist queues the download via the new DownloadRequested flag.
- Move all .NET (6 projects, solution, Directory.*.props, nuget.config)
into backend/ so the monorepo root can host non-.NET projects later
- Remove throwaway test artifacts: player.html, pl.json
- Trim dead Flutter rules from .gitignore
Build from backend/Musify.slnx: 0 warnings, 0 errors.