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.
Musify
A music streaming app. Upload your own tracks, organize them into playlists and albums, and stream them back with a web player built around your listening habits: auto-generated mixes, a home feed, search, likes, and an Explore page for finding something new.
What it does
- Upload and organize your own music: tracks, albums, cover art, artist credits.
- Playlists, likes, and a "recently played" history.
- Mixes, playlists generated automatically from what you actually listen to.
- Explore, genre browsing and search across tracks, albums, playlists and people.
- Real streaming, not just file downloads: seek, queue, shuffle, repeat.
- Accounts via OIDC login, with an optional anonymous listening mode.
- Light and dark themes, and a layout that works on both desktop and mobile.
How it's built
Backend: .NET 10, split into a REST API, a background worker, and a dedicated streaming gateway.
- Clean architecture (Domain / Application / Infrastructure) with CQRS (Mediator) and
ErrorOrresults. - PostgreSQL via EF Core for metadata. RabbitMQ and MassTransit handle async processing (audio transcoding, thumbnails) with saga-based rollback.
- SeaweedFS (S3-compatible) for file storage. Uploads and downloads go straight between the client and storage, never through the API.
- ffmpeg for audio transcoding, ImageSharp for thumbnails.
- A YARP reverse proxy that streams the audio and checks a signed, short-lived ticket on every request.
- Zitadel for login (OIDC/OAuth2).
Web client: SvelteKit 2 on Svelte 5 (runes), TypeScript, Tailwind 4. The session lives on the server, so the backend access token never reaches the browser.
Infrastructure: Musify's own apps run from Docker Compose (dev and
production overlays). Postgres, RabbitMQ, SeaweedFS, Zitadel and Jaeger are
shared infrastructure that lives in a separate Infrastructure git
repository, used by Musify and by other projects that need the same OIDC
provider and object storage.
Project layout
backend/ .NET solution: Api, Worker, StreamingGateway, and the shared Domain/Application/Infrastructure
web-player/ the SvelteKit web client
deploy/ Docker Compose stacks for dev and production
docs/ architecture notes, one file per area
Postgres, Zitadel, SeaweedFS, RabbitMQ and Jaeger are not part of this
layout. They live in the separate Infrastructure repository instead.
Digging deeper
docs/ covers how each piece works and why: architecture, authentication, storage, streaming, media processing, and the local dev setup. deploy/README.md has the full instructions for running the stack, in development or on a production server.