Strip //, /* */ and XML doc comments from all backend .cs files, and
JS/TS/CSS/HTML comments from web-player source (.ts, .svelte, .css),
per the project's no-comments convention.
Add missing AsNoTracking() on read-only queries, drop redundant
Update() calls on already-tracked entities, and fix inconsistent
formatting (stray spaces in generics, ternaries, and lambdas) across
the Albums/Tracks/PlayLists/Users command and query handlers.
Drops the YouTube-backed second catalog source entirely: the
Local/External split on Track/Album collapses into single concrete
entities, Artist/TrackArtist/ExternalTrack/ExternalAlbum and the
YouTube service/download pipeline (yt-dlp, MassTransit consumer,
routing slip) are deleted, and mixes now source discovery only from
local listening history. Includes the RemoveYouTubeIntegration EF
Core migration and matching Worker/deploy/compose cleanup (no more
yt-dlp/python/deno in the Worker image, no thumbnail-cache/YtDlp
config sections).
Backend:
- New Likes feature (toggle/get liked tracks) and endpoints
- User follow/unfollow, listening stats, public user profile
- Playlist visibility (public/private) support
- Various auth and DTO/response adjustments to support the above
Frontend (web-player):
- New design system pass: theme tokens/mode (light/dark), glass menu,
tabs bar, player dock/extras/queue components, mix tiles, artist link
- New routes: Liked, public user profile (/u/[id]), likes API proxy
- Search refactor: shared SearchBox component used by TopBar and
MobileHeader (search now reachable on mobile), live results as you
type instead of requiring Enter, dropped the "Buscando…" quick-result
preview
- Removed now-unused components (MobileNav, CoverBadge, MixBentoTile,
PlayPauseOverlay, SuggestionsTable, userDirectory)
- Design handoff reference docs added under web-player/design_handoff_musify_player
and web-player/docs/design-system-refactor-plan.md
AlbumApplicationResponse used to carry YouTubeAlbumId/ThumbnailUrl as
two always-present-but-usually-null fields for the sake of a single
flat shape. Since one of these fields IS genuinely exclusive to
external albums, an inheritance split reads better than the flattened
shape: AlbumApplicationResponse now holds only the fields shared by
every album, and ExternalAlbumApplicationResponse adds ExternalId
(renamed from YouTubeAlbumId, matching the source-generalized naming
used everywhere else) and ThumbnailUrl.
[JsonPolymorphic]/[JsonDerivedType] are required here, not optional:
without them System.Text.Json serializes strictly by the declared CLR
type at each call site (both the bare return type and nested inside
AlbumSearchItemResponse.Album), so an ExternalAlbumApplicationResponse
instance would silently lose its extra fields on the wire. This also
gives every response a "source" discriminator field for free,
consistent with AlbumSearchItemResponse.Source, and the generated
OpenAPI schema now models it as a proper oneOf/discriminator instead
of nullable fields.
Albums previously had no cover concept at all: no picture upload
flow, no GET /albums/{id}/cover, and AlbumApplicationResponse's
image-key fields were plumbed but never populated. Description,
Pictures and TrackCount already lived on the shared Album base entity
for both local and external albums, so unifying the cover story only
required building the missing local-album picture pipeline.
Backend, cloning PlayList's existing async picture-processing
architecture exactly (MassTransit routing slip + saga), but making
the picture mandatory instead of optional (matching Track's pattern):
- AlbumPictures moves from all-nullable to required fields.
- New AlbumConfiguration (routes/sizes), deliberately without
PlayList's preset-picture fallback since a picture is now required
up front.
- CreateAlbum now takes a mandatory PictureIntentId, validates and
consumes the upload intent, and kicks off the same
copy-to-final -> resize -> upload -> update-entity routing slip
PlayLists use, via a new AlbumProcessingState saga that marks the
album Failed on a picture-processing failure.
- UpdateAlbum takes an optional NewPictureIntentId to replace the
cover later.
- New GET /albums/{id}/cover and POST /albums/upload-picture
endpoints, mirroring the playlist ones.
- New EF migration for the AlbumProcessingState saga table.
Frontend:
- AlbumForm gains a cover dropzone, required on create and optional
on edit (via ImageDropzone's new `required` prop), uploaded through
the same presigned-upload helper playlists use.
- New /api/albums/{id}/cover proxy route.
- PlaylistArt gains a generic coverUrl prop so AlbumHeader and the
album list/detail cards render the real uploaded cover instead of
only the track mosaic fallback.
Delete-time picture cleanup for albums is intentionally left as a
follow-up, matching the existing gap in playlist deletion.
Removes the last YouTube-specific routes (/youtube/*) and merges them
into the general track/album/playlist endpoints, keyed by a `source`
string validated against TrackSource. This mirrors the earlier track
search merge for albums and playlist-track additions:
- GetAlbums now merges local albums with live YouTube Music search
results, paginated via a youtubeContinuationToken, deduped against
already-materialized ExternalAlbum rows.
- New GET /albums/external/{source}/{externalId} and
GET /tracks/external/{source}/{externalId}/stream replace the
YouTube-only equivalents.
- POST /playlists/{id}/tracks replaces the separate
.../youtube-tracks endpoint, accepting an optional Source/ExternalId
pair alongside TrackId.
- AlbumSearchItemResponse/AlbumsSearchResponse follow the same
discriminated-union + trailing-HasNextPage-field pattern already
used for track search, so HasNextPage stays a required field in the
generated OpenAPI schema.
- Frontend routes renamed accordingly (/albums/youtube/[albumId] ->
/albums/external/[source]/[externalId], etc.) with a single
/api/thumbnail proxy replacing /api/youtube/thumbnail.
Musify.Api.Endpoints.YouTubeEndpoints and the standalone
SearchYouTubeAlbums query are removed as a result.
Moves the six backend projects on disk into Core/ and Hosts/
subfolders, matching the solution folders already defined in
Musify.slnx, so the file explorer reflects the same layout as the
solution:
backend/Core/Musify.Domain
backend/Core/Musify.Application
backend/Core/Musify.Infrastructure
backend/Hosts/Musify.Api
backend/Hosts/Musify.Worker
backend/Hosts/Musify.StreamingGateway
Updates every reference to the old paths: ProjectReference entries in
Musify.Api.csproj and Musify.Worker.csproj, the three Dockerfiles,
deploy/scripts/migrate.ps1|sh, deploy/zitadel/provision.ps1,
deploy/up.ps1, deploy/README.md and docs/development.md. Also fixes a
stale Dockerfile comment pointing at a compose file that no longer
exists.
Verified with `dotnet build Musify.slnx` against the new layout.