Commit Graph
27 Commits
Author SHA1 Message Date
Admin 611b2a72cd refactor: tighten the home typography and card craft
The playlist cards were showing "No description was pr…" because the API
stored that English sentence whenever a playlist was created without a
description. Descriptions are optional now and the old rows are cleared.

Section headings drop to 18/20px so they stop competing with the content,
and the space moved from inside the sections to between them.

The three media cards repeated the same anchor, art class and label
block, so they share a MediaCard that also carries the hover lift the
track tiles already had.
2026-07-21 23:14:36 +02:00
Admin 699b36c966 feat: rework the home layout
Recently played keeps its shape. Mixes and playlists now share one row,
half and half on large screens with smaller cards, capped at six each
with a link to the full list. Albums are the ones the user listened to
most recently, which is a new query joining the listening history with
the album tracks. The endless track wall is now "Descubrir".
2026-07-21 19:06:36 +02:00
Admin 584109bf05 feat: bring over the albums, explore and player work from the worktree
Moves the remaining uncommitted work: the home gets quick mixes and a
"Tus álbumes" shelf, saving a YouTube album now goes through
addYoutubeAlbumToPlaylistAction instead of the SaveYouTubeAlbum command,
and UserAlbum drops its unused ExternalId.

The mixes section keeps its current shape (no refresh action, no badge,
hidden when empty) rather than the older version the worktree carried.
2026-07-21 18:18:23 +02:00
Admin 90e842d084 feat: recommend server-generated mixes from Musify and YouTube Music
Mixes are their own concept, not playlists: they live in their own
Mixes/MixItems tables, cannot be edited and never show up among the
user's playlists. Each item is either a Musify track or a YouTube Music
reference, so YouTube songs stream directly instead of being downloaded.

A daily Hangfire job seeds the mixes from the listening history: one
discovery mix, one daily mix and one per top artist.

The per-user YouTube filler query was already in the working tree and
rides along here because it shares files with this change.
2026-07-21 18:13:07 +02:00
Admin fe6b0b7268 feat(backend): save a YouTube album with all of its tracks
POST /albums/youtube/{albumId} creates the album in your library and links
every song in it, provisioning each one as an external track without queueing
any download. The external id is kept on the album so saving it twice returns
a conflict instead of a duplicate, and albums can now hold external tracks,
not just the ones you uploaded.
2026-07-20 20:47:50 +02:00
Admin 07e1daf2ce feat(backend): search albums on YouTube Music
Two read-only endpoints: the album search and the album detail with its
tracklist. Both go through the same single-flight cache as the song search,
and the detail one especially benefits since it costs two upstream calls, the
browse id lookup and the album itself, for metadata that never changes.
Paginator handling is now shared between the song and album searches.
2026-07-20 20:36:28 +02:00
Admin 22ed923a04 feat(api): expose the album endpoints
Eight routes under /albums covering the listing, the detail, the tracklist and
the owner-only writes, wired to the same validation filter and ErrorOr mapping
the playlists use.
2026-07-20 20:13:40 +02:00
Admin 6490ae9822 fix(backend): give track titles room to breathe
Titles were capped at 50 characters and the YouTube provisioner silently cut
them there, which mangles anything with a feature or a remix suffix. Albums
are about to make that far more visible, so the column now holds 200.
2026-07-20 20:06:47 +02:00
Admin 3cbf64a191 fix(backend): keep the saved cover art at full resolution
Search results now carry a small thumbnail, so the artwork downloaded when a
YouTube track is saved would have shrunk with them. The URL is upsized to the
configured artwork resolution right before the download event goes out.
2026-07-20 17:42:11 +02:00
Admin c302cfd314 perf(backend): cache YouTube searches and shrink the thumbnail URLs
Repeated searches and song lookups now reuse a single-flight memory cache, so
concurrent or debounced queries hit YouTube once instead of on every keystroke.
Thumbnails are resolved at a configurable size instead of the largest variant,
which cuts the image payload the browser pulls from Google.
2026-07-20 17:29:28 +02:00
Admin 6156101029 refactor(backend): clean up the AppSettings files
Drop the dead Keycloak and ConnectionStrings sections from the Worker and move
its hardcoded dev values to AppSettings.Development.json, so the base file is
the empty template the convention describes. Remove the stale Authentication
block from the API dev settings (provisioning writes those to user-secrets) and
the ManifestFileName left over from DASH. Point the stream-ticket key and the
Worker scratch directory at deploy/keys and deploy/.tmp instead of D:\weed and
D:\tempsFilesDev, and make the API listen on localhost instead of a fixed LAN
address.
2026-07-20 15:13:27 +02:00
Admin 99c4bb8408 Changes 2026-07-18 23:29:57 +02:00
Admin ca275fe7a8 refactor(backend): unify YouTube track creation via a provisioner
Track get-or-create for YouTube videos was duplicated between the
old GetYouTubeTrackStream query handler (which also wrote to the
database) and AddYouTubeTrackToPlayList, with frontend-supplied
metadata (title/artist/duration/thumbnail) trusted at add-to-playlist
time instead of resolving it server-side.

Extract YouTubeTrackProvisioner as the single get-or-create path,
resolving structured artist refs (id + name) from YouTube Music so
multi-artist songs map to distinct Artist rows. Rename
GetYouTubeTrackStream to ResolveYouTubeTrackStream (it writes, so it
is a command, not a query) and delegate to the provisioner.
AddYouTubeTrackToPlayList slims down to (UserId, PlayListId, VideoId)
since all metadata now comes from the provisioner/YouTube API.
2026-07-17 17:56:37 +02:00
Admin 24595a17d6 feat(backend): auto-download YouTube tracks saved to playlists 2026-07-16 01:06:04 +02:00
Admin 2bd5ef729e feat(backend): YouTube Music search and playback resolution endpoints 2026-07-16 01:05:42 +02:00
Admin 461bae1b01 feat(backend): add playlist cover endpoint and relax description validation
Adds GET /playlists/{id}/cover (mirrors the track cover endpoint) so
uploaded playlist pictures can actually be served. Also drops the
NotEmpty rule on playlist description, which rejected the blank
description the frontend treats as optional; CreatePlayList now falls
back to the domain's default text instead.
2026-07-15 16:09:40 +02:00
Admin 84ffb1eef6 Changes 2026-07-14 19:11:58 +02:00
Admin 0b72363c33 feat(tracks): add track cover endpoint
Serve cover images via GET /tracks/{id}/cover with a size parameter, streamed from storage with long-lived immutable cache headers.
2026-07-14 04:54:40 +02:00
Admin a40f3da247 feat(tracks): add user listening history
Record a listening-history entry when a stream ticket is issued and expose GET /users/{id}/listening-history to read it back.
2026-07-14 04:54:35 +02:00
Admin 927f965023 fix(playlists): reject empty/whitespace name and description on update 2026-07-06 02:20:47 +02:00
Admin ac1c515dec feat(tracks): support search by name in GetTracks 2026-07-06 02:04:01 +02:00
AdminandClaude Opus 4.8 a40de35a57 refactor(api): OpenAPI response metadata, cached user sync, ProblemDetails
- Document every endpoint's responses (Produces<T>/status codes/validation)
  so the generated OpenAPI/Scalar docs carry response schemas and status codes
  instead of empty responses
- Cache recently-synced user ids (IMemoryCache, 15 min) in the JWT
  TokenValidated handler so user provisioning no longer hits the DB on every
  authenticated request
- Add ProblemDetails + UseExceptionHandler for consistent RFC7807 errors
- Add ToCreatedResult ErrorOr helper and use it in the create endpoints
- Normalize the playlists route to lowercase (/playlists)

Verified: booted the Api against RabbitMQ + Postgres in Docker and inspected
/openapi/v1.json — operations now expose response schemas/status codes and the
route is /playlists.

Build: 0 warnings, 0 errors.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 21:04:15 +02:00
Admin d6c269d80f Formatting 2026-07-05 16:04:53 +02:00
AdminandClaude Opus 4.8 38f7094fb0 refactor(di): add AddApplication entry point and generic AddValidatedOptions
- Add Musify.Application.AddApplication(services, config) as the layer's DI
  entry point: registers Mediator, UploadIntentValidator and all app configs
- Collapse the 5 near-identical AddXConfiguration methods into a single
  generic AddValidatedOptions<T>(config, section)
- Move UploadIntentValidator registration out of Infrastructure into the
  Application layer (fixes layering leak); Infra config binding now uses the
  generic helper
- Api calls AddApplication; Worker uses AddValidatedOptions for its configs

Build: 0 warnings, 0 errors.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 04:36:46 +02:00
AdminandClaude Opus 4.8 e802c8565d refactor(application): co-locate each command/query with its handler
One file per use case under the feature folder (Tracks/PlayLists/Users),
dropping the Commands/Queries/Handlers sub-folders and their namespaces.
Each file now holds the record and its handler in namespace
Musify.Application.<Feature>; external usings updated accordingly.

Build: 0 warnings, 0 errors.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 04:12:59 +02:00
AdminandClaude Opus 4.8 6e469dbb4a refactor: replace Ardalis.Result with ErrorOr across the app
Ardalis Result<T> carried incoherent success sub-statuses and a clunky
generic converter; ErrorOr models success-or-errors cleanly.

- Application: all commands/queries/handlers now return ErrorOr<T>
  (non-generic Result -> ErrorOr<Success>); errors via Error.NotFound/
  Conflict/Validation/Unauthorized/Failure; success via value or new Success()
- Drop ResultExtensions.As<>(): error propagation is now just return .Errors
- Rewrite HTTP mapping (ErrorOrHttpExtensions) off Error.Type; success maps
  to 200 (value) or 204 (Success); Created still built in the endpoints
- Endpoints/gateway middleware: !IsSuccess -> IsError
- Swap Ardalis.Result package for ErrorOr 2.1.1 (remove unused ref from Worker)

Build: 0 warnings, 0 errors.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 04:05:43 +02:00
AdminandClaude Opus 4.8 8f34ed4874 chore: group .NET into backend/ and drop non-.NET test artifacts
- 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.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 03:43:52 +02:00