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.
3.5 KiB
Almacenamiento (SeaweedFS / S3)
Qué es
SeaweedFS es el almacén de objetos, compatible con la API S3. Guarda los originales subidos y los derivados (audio .m4a, miniaturas). Se accede vía AWSSDK.S3 desde StorageService (IStorageService).
Expone:
- S3 API (
:8333) — operaciones S3 (presigned URLs, put/get/list…). - filer HTTP (
:8888) — sirve los ficheros por ruta; lo usa el StreamingGateway. - master (
:9333) y volume server — internos.
Bucket de la app: webapi-storage (ApplicationStorage:Bucket). Importante: la API y el Worker usan el mismo bucket (el worker procesa lo que sube la API).
Estructura de claves (layout)
temp/{userId}/Tracks/{guid}.{ext}— subidas temporales (objetivo de las presigned PUT, antes de consumir el intent).uploads/{userId}/Tracks/OriginalPictures|OriginalAudios/...— originales ya consumidos.Tracks/SmallPictures|MediumPictures|LargePictures/...— miniaturas generadas.Tracks/ProcessedAudios/{AudioFolderName}/— audio transcodificado (audio.m4a, AAC con faststart).
Las claves se construyen siempre con el helper StorageKey.Combine(...) (en Application/Shared), que une segmentos con / limpiando espacios y barras. Está centralizado para estandarizar y no repetir lógica.
En el filer, los objetos S3 viven bajo
/buckets/{bucket}/{key}— de ahí que el gateway reescriba/media/{key}→/buckets/webapi-storage/{key}.
URLs prefirmadas (presigned)
Para no hacer pasar ficheros grandes por la API, el cliente habla directo con S3 mediante URLs firmadas de corta vida:
- Subidas (PUT):
RequestTrackUploadUrls/RequestPlayListPictureUploadgeneran presigned PUT (conContent-TypeyIf-None-Match: *firmados, expiración ~120s). El cliente sube directo a S3. - Imágenes (GET): las miniaturas se sirven con presigned GET (un objeto = una URL).
Por qué presigned y no proxiar: subir un audio de ~100 MB a través de la API consumiría memoria/ancho de banda de los servidores de aplicación y escalaría mal. La presigned PUT directa a S3 es el patrón estándar y correcto.
Upload intents y cuota
Cada upload-urls crea UploadIntents (reservas en estado Issued) y devuelve las URLs. Al crear la pista/playlist se consumen. Hay cuota por usuario (MaxActiveUploadIntentsPerUser, bytes…) para evitar abuso. El UploadIntentExpirationJob (en el Worker) marca como expirados los intents caducados y libera la cuota; si ese job no corre, los intents colgados se acumulan y bloquean nuevas subidas.
Qué es público y qué privado (despliegue)
| Exponer a internet | Mantener privado |
|---|---|
S3 API :8333 (tras TLS) — necesario para presigned PUT/GET del cliente |
filer :8888 (solo el gateway, en red interna) |
StreamingGateway :8081 |
master :9333, volume server, UI de admin |
- El audio se sirve por el gateway → SeaweedFS puede quedar privado para ese flujo.
- Subidas e imágenes usan presigned → el cliente toca el endpoint S3 directamente, así que
:8333debe ser alcanzable (pero inútil sin firma válida; es el modelo de cualquier bucket S3/MinIO). - En producción:
InfrastructureStorage:Addressdebe ser el endpoint S3 público (la firma se calcula para ese host); si los servidores no lo alcanzan internamente, se usa config dividida (host público para firmar / interno para operaciones server-side).
Regla: el gateway y, como mucho, el :8333 firmado son la única superficie pública; master/volume/filer/admin nunca se exponen.