Tracks
- Track.IsPublished (migration marks existing tracks as published). New
tracks are created unpublished.
- PUT /tracks/{id}/published and PUT /tracks/published change the flag;
publishing requires completed audio processing (the batch endpoint
reports skipped tracks).
- An unpublished track is only visible to its owner. A WherePublished /
VisibleTo filter is applied to search, genres, listening history, last
listened, likes, mixes, album and playlist contents and counts, recently
listened albums, public user tracks, track details and stream tickets.
- isPublished is part of the track responses, GET /tracks/users/{userId}
accepts a published filter and status-counts reports published and
unpublished totals.
- CreateTrackRequest.pictureIntentId is optional: without it the track
uses the album cover or the preset picture and no picture processing
runs. CreateTrackResourcesEvent carries nullable picture fields, so
queues must be drained before deploying.
- POST /tracks/upload-urls accepts an audios list and one optional picture
shared by all of them, and returns an intent per file. The upload intent
limits are raised so a batch fits.
Albums
- AlbumType (Album, EP, Single) on create, update and responses.
- CreateAlbumRequest.trackIds creates the album and links the tracks in a
single save.
GET /tracks/users/{userId} accepts repeated status values and returns the
albums each track belongs to. GET /tracks/users/{userId}/status-counts,
GET /albums/{id}/tracks/ids and DELETE /tracks with a list of track ids
are new.
Adapt the refactored web player to the new backend API (id path params,
bulk track adds, flat list items, mix kind) and port AddAlbumToPlayList
to the backend conventions.
appsettings.Development.json still targeted Musify's old own Postgres
(port 59000) with the superuser, the default RabbitMQ vhost as guest and
the admin S3 keys. They now use Infrastructure's dev ports and Musify's
own dev credentials (role, vhost and S3 identity), the same defaults as
deploy/.env.example.
The API's non-secret Zitadel settings (issuer, metadata, endpoints) move
there too, so user-secrets only need the per-machine client id.
Infrastructure now gives each project its own Postgres role, RabbitMQ
user and vhost, S3 identity limited to its bucket and a Zitadel service
account that owns only its project, instead of handing out the admin
accounts.
- Postgres, RabbitMQ and S3 settings default to Musify's own (musify).
- MassTransit connects to the `musify` vhost (RABBITMQ_VHOST, taken from
the address path); RABBITMQ_DEFAULT_USER/PASS become RABBITMQ_USER and
RABBITMQ_PASSWORD.
- zitadel-init mounts ZITADEL_PAT_DIR (Infrastructure's
zitadel/.output/musify) and uses that PAT. It no longer creates the
Zitadel project, which that account can't do: Infrastructure does.
- Android/iOS now return to cat.ikerdemo.musify:/callback instead of
musify://callback: a private-use scheme based on a domain we own
(RFC 8252 section 7.1), so another app is unlikely to claim it.
- The Musify Native app no longer needs devMode: loopback and custom
scheme redirects are both valid for a native app.
- zitadel-init also updates an existing app's config, so the change
reaches instances created before it (the client id is kept).
- iOS takes the scheme from everything before the first ':', since the
new URI has a single slash.
The shared Infrastructure stack now runs the Login V2 container, and
turning the feature off here would switch every project on the shared
instance back to the old login.
Add catch-all routes so unknown pages 404 inside the (app) layout and unknown
/api URLs return a JSON 404 instead of redirecting to the login page. Restore
the full root error page.
Use invariant culture when formatting ids, put cancellation tokens last in endpoint handlers, remove the
CS8618 pragmas and keep only the analyzer overrides that still apply.
Infrastructure exposes its internals to the test project so the activities and slip builders can be
exercised directly, and the fault consumer and state machines run on the MassTransit test harness.
The Worker now registers the Application layer, observability and stream tickets, validates the
container on build and ships a complete appsettings. The Gateway drops underscore fields and uses a
placeholder bucket in its default YARP transform.
Guid and long route constraints, id/trackId parameter names, Update*Request without the New prefix,
sentence-case operation summaries, shared security scheme id and a single Limits class.
The player dock used to sit fixed over the scrolling page, so it needed an
opaque background. The shell is now a fixed-height frame where only the
content column scrolls, letting the dock stay transparent like the sidebar
and queue. Scroll reset/restore on navigation moves to restoreScroll(), and
the stable scrollbar gutter moves from <html> to the content scroller.
Following lists are public; followers are visible to the user and to mutual followers.
Adds GET /users/{id}/followers and /following, exposes isFollowing and canViewFollowers
on the profile, and returns isFollowedByViewer from user search so the web player no
longer calls /is-following per user.
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.
The counted-listen tests lived in one catch-all UncountedListensTests
file. Each now sits next to the handler it covers, and the cleanup
handler gets its own test file.