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).
7.1 KiB
Entorno de desarrollo
Cómo levantar todo en local y probarlo. El detalle completo del deploy (y el de producción) está en deploy/README.md.
Arranque
docker compose -f deploy/compose.yml -f deploy/compose.dev.yml up -d
Ese único comando levanta la infraestructura y corre los jobs one-shot de
bootstrap: genera las claves RS256 de stream-ticket en deploy/keys/, aplica
las migraciones EF Core y provisiona Zitadel (proyecto + apps OIDC), dejando
los client ids en deploy/.env. Detalle completo, incluido cómo arrancar las
apps en Docker con --profile apps, en deploy/README.md.
Aplicaciones
dotnet run --project backend/Hosts/Musify.Api # :5111
dotnet run --project backend/Hosts/Musify.StreamingGateway # :8081
dotnet run --project backend/Hosts/Musify.Worker # background
npm --prefix web-player run dev # :5173
Mapa de puertos
| Servicio | Puerto |
|---|---|
| Musify.Api | 5111 (/scalar/v1, /openapi/v1.json) |
| Musify.StreamingGateway | 8081 (/health, /media/...) |
| web-player | 5173 (npm run dev) o 3000 (contenedor) |
| SeaweedFS S3 / filer / master | 8333 / 8888 / 9333 |
| Zitadel | 8080 |
| PostgreSQL | 59000 (→ 5432 en el contenedor) |
| RabbitMQ AMQP / management | 5672 / 15672 |
| Jaeger UI / OTLP | 16686 / 4317 |
pgAdmin (-Tools) |
5050 |
DB: musify_db, usuario postgres/postgres. Bucket S3: webapi-storage
(credenciales admin_access_key/admin_secret_key). Todos los valores salen de
deploy/.env.
Migraciones
El servicio migrate de deploy/compose.yml las aplica en cada docker compose up -d (no-op si el esquema ya está al día). A mano, tras cambios de
esquema, contra la base local:
dotnet ef database update --project backend/Core/Musify.Infrastructure --startup-project backend/Core/Musify.Infrastructure --context Database
Musify.Infrastructure es a la vez el proyecto de migraciones y el de arranque:
tiene el IDesignTimeDbContextFactory y el paquete EFCore.Design. La cadena
de conexión se lee de sus user-secrets, o de la variable de entorno
Database__ConnectionString si no hay user-secrets (así es como la usa el
contenedor migrate, ver backend/Musify.Migrator.Dockerfile).
Stream-tickets (claves RS256)
La API firma los stream-tickets con la clave privada y el gateway los valida con
la pública. El servicio keys-init de deploy/compose.yml las genera en
deploy/keys/ si no existen; a mano:
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out deploy/keys/stream_private.pem
openssl rsa -in deploy/keys/stream_private.pem -pubout -out deploy/keys/stream_public.pem
- API:
StreamTicket:PrivateKeyPath. - Gateway:
StreamTicket:PublicKeyPath.
Tests
dotnet test backend/Musify.slnx
backend/Tests/ (carpeta /Tests/ en el .slnx) tiene cinco proyectos xUnit:
| Proyecto | Cubre | Docker |
|---|---|---|
Musify.Domain.Tests |
Lógica de los value objects (TrackAudio/TrackPictures: IsProcessed, IsFailed, IsInProgress). Las entidades son en su mayoría anémicas — sin comportamiento propio, nada más que testear ahí. |
No |
Musify.Application.Tests |
Los ~45 handlers y servicios de Musify.Application (Albums, Tracks, PlayLists, Users, Mixes). |
No |
Musify.Infrastructure.Tests |
Database/migraciones, StorageService, StreamTicketService, AuditableEntityInterceptor, PictureService, SingleFlightCache, PlayListPresetSeeder. |
Sí |
Musify.Api.Tests |
Endpoints HTTP reales (routing, auth, ValidationFilter, mapeo ErrorOr→HTTP) vía WebApplicationFactory, más ErrorOrHttpExtensions/CurrentUser/validators FluentValidation en aislado. |
Sí |
Musify.StreamingGateway.Tests |
TicketValidator (RS256) y TicketValidationMiddleware (traversal, límites de prefijo, extracción de ticket). |
No |
Musify.Worker no tiene proyecto de test: su Program.cs es puro wiring de
DI/host (Hangfire, MassTransit, jobs) — la lógica real que ejecuta vive en
Musify.Infrastructure, ya cubierta ahí.
Application.Tests: por qué SQLite en memoria y no mocks
IDatabase expone DbSet<T> directamente, así que los handlers arman LINQ
real (Where, Include, Select...) contra él — no se puede mockear eso con
NSubstitute, no hay IQueryable detrás de un mock. En su lugar,
TestSupport/TestDatabase.cs es un DbContext real sobre SQLite en memoria
(una conexión :memory: nueva por test, con foreign keys reales — a diferencia
del proveedor InMemory de EF Core, SQLite además soporta transacciones, que dos
handlers usan). TestSupport/TestEntities.cs y TestConfigurations.cs
construyen entidades y configuración con valores por defecto sensatos.
IStorageService, IEventBus, etc. sí se mockean con NSubstitute, al ser
interfaces normales.
Infrastructure.Tests y Api.Tests: Testcontainers
Estos dos sí necesitan Docker corriendo. Levantan un Postgres real
(Testcontainers.PostgreSql) y, en Infrastructure.Tests, también un
SeaweedFS real (mismo chrislusf/seaweedfs:latest e imagen/comando que
deploy/compose.yml, no MinIO) — así StorageService se prueba contra el
mismo backend S3-compatible que usa producción, no contra AWS S3. Los
contenedores se levantan una vez por corrida (ICollectionFixture), con
migraciones EF Core reales aplicadas contra el Postgres efímero.
Api.Tests usa WebApplicationFactory<Program> contra ese mismo Postgres —
routing, auth y validación reales — pero sustituye por fakes los servicios que
hablan por red (IStorageService, IEventBus, IStreamTicketService) y el
esquema de autenticación JWT real por un handler
de prueba (ver TestSupport/FakeAuthenticationHandler.cs) que autentica según
un header, sin necesitar un Zitadel real.
Escribir estos tests contra el backend real encontró y arregló dos bugs reales
(no simulados): StorageService.RemoveFolderAsync reventaba con
NullReferenceException contra SeaweedFS (su respuesta DeleteObjects omite
la lista <Error> cuando no hay errores, a diferencia de AWS S3), y
AuditableEntityInterceptor nunca actualizaba UpdatedAt en una actualización
normal (carga → modifica → guarda) porque corría antes de que EF detectara los
cambios pendientes.
Config (AppSettings)
Convención: AppSettings.json es la plantilla con valores vacíos o neutros;
AppSettings.Development.json tiene los valores reales de dev; en Docker todo
se sobreescribe con variables de entorno (Seccion__Clave) desde
deploy/compose.yml. Para correr Musify.Api desde el IDE, los valores de
Authentication van en user-secrets (no en el repo) — cópialos de
deploy/.env una vez que zitadel-init los haya generado (ver
deploy/README.md).
Secciones clave: Authentication (Zitadel), Database, MassTransit,
InfrastructureStorage (S3), ApplicationStorage (bucket), Track, PlayList,
UploadIntent, AudioTranscoder, Workers, StreamGateway / StreamTicket.
Requisitos del host
- .NET 10 SDK y Node 22.
- ffmpeg en el PATH (transcode de audio del Worker).
- Docker Desktop.