Commit Graph
23 Commits
Author SHA1 Message Date
Admin b61ff1ebec Make Discover and upload genres dynamic
CI/CD / BackEnd (push) Successful in 1m22s
CI/CD / WebPlayer (push) Successful in 38s
CI/CD / Deploy (push) Skipped
CI/CD / BackEnd (pull_request) Successful in 1m21s
CI/CD / WebPlayer (pull_request) Successful in 40s
CI/CD / Deploy (pull_request) Skipped
Discover used a hardcoded list of genre tiles that searched track titles,
and the upload form kept its own copy of the genre list and compatibility
rules. Both now come from the backend.

Backend:
- GET /genres returns genres with at least one track and their counts.
- GET /genres/available returns every assignable genre with the genres
  it cannot be combined with, so the rules live only in
  GenreCompatibility.
- GET /tracks accepts ?genre= (case-insensitive, 400 on unknown values).

web-player:
- Discover tiles are built from /genres; each opens /explore?genre=X, a
  song list filtered by that tag. Unknown genres fall back to a label
  derived from their name.
- The upload form loads /genres/available, disables incompatible genres
  as you pick, and the server validates before reserving upload URLs.
- lib/data/trackGenres.ts is removed; its helpers merge into
  lib/data/genres.ts.
- Regenerate the typed API client.
2026-09-23 20:05:30 +02:00
Admin 5df5e635f3 Count real listening time instead of full track duration
"Listening time this week" summed the full duration of every track whose
stream ticket was issued, so pressing play and skipping counted the whole
song. Playback is now measured on the client and reported to the API.

Backend:
- ListeningHistory gains PlayedSeconds, LastProgressAt and IsCounted
  (migration TrackPlayedSeconds marks existing rows as counted).
- /tracks/{id}/stream returns a listenId; new
  PUT /tracks/listens/{listenId}/progress stores the accumulated seconds,
  capped by track duration and elapsed wall-clock time, monotonic and
  owner-only.
- A listen counts once it reaches min(30s, 50% of the track). Weekly
  stats, streak, recent history/albums, mixes and listensCount only use
  counted listens; seconds sum PlayedSeconds.
- ListeningHistoryCleanupJob (daily) deletes uncounted listens older
  than 8 days.

web-player:
- ListenTracker accumulates played time (seeks are ignored) and flushes
  on pause, track change, ended, every 30s, and on pagehide.
- Proxy route /api/listens/[id]/progress; repeat starts a new listen per
  loop.
2026-09-23 20:00:45 +02:00
Admin a7929c0ac9 Enhance upload functionality with genre selection and validation; refactor session cookie handling
CI/CD / WebPlayer (push) Successful in 39s
CI/CD / Deploy (push) Canceled after 0s
CI/CD / BackEnd (push) Canceled after 39s
2026-09-23 18:33:21 +02:00
Admin ba531b4f61 Add actions 2026-09-23 05:28:11 +02:00
Admin 2662abe460 Remove comments from backend and web-player source
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.
2026-09-21 17:12:18 +02:00
Admin c14db5a711 Checkpoint web-player design-system refactor and backend track genre tagging
Advances the ongoing token/componentization cleanup (anchored player dock,
border-based panels instead of floating shadows, corrected light-mode
accent/gradient tokens) alongside in-progress backend support for track
genre tags.
2026-09-17 17:43:37 +02:00
Admin 5ae9955bd0 Remove YouTube integration from web-player; unify Home track rows
Drops every YouTube-backed route, proxy and type (external
albums/tracks, thumbnail proxy, youtube.ts) and simplifies
player/track/mix types to the local-only catalog now that the
backend contract dropped Source/ExternalId. Genre tiles on Explore
and "Populares esta semana" on Home stay as deliberate client-only
placeholders per product decision, just without the YouTube backing.

Also finishes the design-system unification pass: Home's three
hand-rolled track rows (Continuar escuchando, Playlist destacada,
Populares esta semana) now reuse the shared TrackTitleCell component
instead of duplicating cover+title+artist markup, and the orphaned
Rail.svelte (no importers) is removed.
2026-09-15 21:17:14 +02:00
Admin dc1ba89a6d Add likes, follows and playlist visibility; redesign web-player UI
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
2026-09-14 23:42:45 +02:00
Admin 389b3b9244 Add anonymous listening support and redesign web-player UI
Backend:
- Expose GET /config/playback so the frontend can detect whether
  anonymous listening is enabled without requiring auth
- Wire anonymous access through track streaming and YouTube resolve flows

Frontend:
- Allow browsing/listening without a session when the backend enables it
- Redesign Inicio, Sidebar and top bar with a new dark theme, typography,
  and layout (top bar with live quick-search, sticky/blurred on scroll)
- Populate Top música, Popular and Recomendados with real Musify +
  YouTube Music data instead of placeholders
- Various shape, spacing, color and animation refinements across the
  redesigned components
2026-09-11 01:29:37 +02:00
Admin 10766bbb47 Split TrackApplicationResponse into a Local/External hierarchy
Same treatment as AlbumApplicationResponse, but Track is the cleaner
case: OwnerUserId (local-only) and ExternalId (external-only) are
both genuinely exclusive to one side, so the base carries neither and
is abstract — every real value is one of the two derived types.

    TrackApplicationResponse (abstract, shared fields + FromEntity dispatch)
      LocalTrackApplicationResponse    (+ OwnerUserId)
      ExternalTrackApplicationResponse (+ ExternalId)

Hit a real gotcha along the way: the response already had an explicit
Source property, which collides with the "source" discriminator STJ's
polymorphism support injects ("contains property 'source' that
conflicts with an existing metadata property name" — only surfaced at
runtime in the Api integration tests, not at build time). Fixed by
dropping the now-redundant explicit Source field entirely; the
discriminator already serializes the identical "Local"/"YouTube"
value under the same name, so the wire contract is unchanged. Updated
the two call sites that read .Source directly (TrackSearchItemResponse
and a playlist track filter that needed flatMap instead of
filter().map() to keep the narrowing across the chain).
2026-09-10 19:19:59 +02:00
Admin 478b90d75c Make album cover pictures mandatory, unify cover handling with playlists
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.
2026-09-10 15:04:36 +02:00
Admin 92a06a3c15 Combine track search with live YouTube Music results
GET /tracks now merges the local catalog page with a live YouTube Music
search (deduplicated against tracks already provisioned locally) whenever a
name filter is given, appending the YouTube hits after the local matches and
carrying a continuation token for further pages. Each item is tagged with its
source via the new TrackSearchItemResponse/TracksSearchResponse shapes,
wrapping the existing TrackApplicationResponse or a raw YouTubeSongResult so
neither type had to change.

GET /youtube/search is removed along with its now-unused handler; the
web-player's explore page, its /api/tracks proxy, and the YouTube filler
helper are updated to call the combined endpoint instead of hitting the two
separately.
2026-09-10 09:55:58 +02:00
Admin f54ebec0eb Changes 2026-07-25 01:46:59 +02:00
Admin 6412a657e7 Changes 2026-07-24 18:54:31 +02:00
Admin 0d34a40931 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 74066ea57d refactor: drop dead code left after the worktree move
ChipGroup is orphaned since the explore rewrite dropped the filter chips.
Mix.GeneratedOn was written on every generation and never read, and it
duplicated CreatedAt, which IAuditable already sets at the same moment.
2026-07-21 18:22:50 +02:00
Admin 2f98982d83 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 c1a9485d24 perf(web-player): take the cover track ids from the list response 2026-07-21 18:13:46 +02:00
Admin 496a5f4a0b 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 62b0ab37a9 feat(web-player): page the album results and save YouTube albums
The albums section keeps a Ver más button while YouTube Music has another
page, fed by a new /api/youtube/albums route that forwards the continuation
token. The YouTube album page gains a save button that drops the whole album,
tracks included, into your library and takes you to it.
2026-07-20 20:49:56 +02:00
Admin 29c220a1cf feat(web-player): show YouTube Music albums in the explore search
The Albums chip now returns both your own albums and what YouTube Music has
for the term. A YouTube card opens a read-only album page with its cover,
tracklist and play button, and right-clicking a track still offers to add it
to a playlist through the existing menu.
2026-07-20 20:39:21 +02:00
Admin 9cd916c098 fix(web-player): follow the API types where they are nullable
The checked-in schema was stale and typed artist, description and externalId
as always present, so the code trusted values the API returns as null. The
regenerated schema tells the truth; the API-shaped types now admit null and it
is normalised on the way into the player, where the state stays non-null.
2026-07-20 20:20:34 +02:00
Admin 97daa13ebd refactor: rename frontend/ to web-player/ and format with prettier
Updates the compose build context, the Dockerfile header, the package
name and the docs. The compose service and container keep the name
frontend/musify-frontend, since nginx routes to http://frontend:3000.

The source files also carry the first prettier pass, so the rename and
the reformat land together for them.
2026-07-20 07:35:01 +02:00