The root README was a design handoff for a prototype that is not in this repo, web-player/README.md was the sv template, and deploy/README.md only covered development. Replace all three and document the production deploy: its prerequisites, what up.sh does and why nginx sits in front. The docs under docs/ still used the pre-monorepo project paths, Ardalis.Result instead of ErrorOr and the player.html test console that no longer exists.
4.0 KiB
4.0 KiB
Arquitectura
Principios
- Clean architecture / capas en
backend/: el dominio y la aplicación no dependen de infraestructura; la infraestructura implementa las interfaces (Application/Contracts). - CQRS: cada operación es un
CommandoQuerycon su handler (Mediator). Los handlers devuelvenArdalis.Result(Ok/NotFound/Unauthorized/Conflict/Invalid…), que la API traduce a códigos HTTP. - Los bytes pesados no pasan por la API: subidas con URLs prefirmadas (cliente → S3) y reproducción vía un reverse proxy dedicado (gateway → S3). La API solo mueve metadatos y emite permisos.
- Procesado asíncrono: crear una pista no transcodifica en caliente; publica un evento y un worker hace el trabajo pesado con compensación (sagas/routing slips de MassTransit).
Componentes
| Componente | Responsabilidad | Por qué separado |
|---|---|---|
| Musify.Api | API REST (usuarios, pistas, playlists), autoriza, emite presigned URLs y stream-tickets | Punto de entrada fino; no procesa medios ni sirve bytes |
| Domain/Application/Infrastructure | Dominio + casos de uso + infraestructura compartida (EF, S3, MassTransit, ffmpeg) | Lógica reutilizada por la API y el Worker |
| StreamingGateway | Reverse proxy (YARP) que valida el ticket y sirve el audio (.m4a) desde SeaweedFS |
Escala con el tráfico de bytes, independiente de la API; SeaweedFS queda privado detrás |
| Worker | Consume eventos de MassTransit: transcodifica audio a .m4a y genera miniaturas |
Trabajo CPU/IO intensivo fuera del request HTTP |
| PostgreSQL | Metadatos (usuarios, pistas, playlists, upload intents) | — |
| RabbitMQ | Cola de eventos + outbox transaccional | Desacopla creación de procesado |
| SeaweedFS | Almacenamiento de objetos (S3 + filer) | Guarda originales y derivados (audio .m4a, miniaturas) |
| Zitadel | Identidad (OIDC/OAuth2) | Login y emisión de JWT |
Flujos principales
1. Subida de una pista
- Cliente pide
POST /tracks/upload-urls→ la API crea UploadIntents (reservas) y devuelve URLs prefirmadas PUT (imagen + audio). - Cliente sube los ficheros directo a SeaweedFS con esas URLs (no pasan por la API).
- Cliente llama
POST /trackscon losintentIds → la API valida los intents, crea elTrack(+UserHasTrack) y publicaCreateTrackResourcesEvent. - El Worker consume el evento: mueve los originales a su sitio, genera miniaturas y transcodifica el audio a
.m4a, y actualiza el estado del track.
Ver detalle en media-processing.md y storage.md.
2. Reproducción (streaming)
- Cliente pide
GET /tracks/{id}/stream(autenticado) → la API autoriza y devuelve{ manifestUrl, ticket }(URL al.m4a+ ticket RS256 acotado a la carpeta de esa pista). - El reproductor abre la URL del audio añadiendo
?t=<ticket>(o cabeceraX-Stream-Ticket) y descarga por HTTP range. - El StreamingGateway valida el ticket y reenvía al filer de SeaweedFS; los bytes van storage → cliente.
Ver streaming.md.
3. Autenticación
- Cliente hace login OIDC contra Zitadel y obtiene un access token (JWT).
- La Musify.Api valida la firma/iss/aud del JWT.
- En cada token validado, se sincroniza el usuario local (crea/actualiza nombre y foto) — provisioning just-in-time.
Ver authentication.md.
Modelo de datos (entidades)
- User —
Ides elsubnumérico de Zitadel (long), más nombre yProfilePictureUrl. - Track — pista: nombres de originales/derivados,
AudioFolderName(carpeta del.m4atranscodificado), estados de procesado (ProcessingStatus),LifeCycleStatus. - PlayList — lista del usuario (
UserId). - PlayListHasTrack — pistas dentro de una playlist (con
Position). - UserHasTrack — propiedad: qué usuario posee qué pista.
- UploadIntent — reserva de subida (clave, bucket, tamaño esperado, estado, expiración) para validar y aplicar cuota.
- Upload — registro de objetos subidos/transferidos al storage.