Translate docs to English, rewrite README, drop design handoff folder
Translate all of docs/ from Spanish to English and rewrite the root README as a short project overview instead of a technical run guide. Remove web-player/design_handoff_musify_player, an old design-tool export that was no longer needed.
This commit is contained in:
+30
-30
@@ -1,48 +1,48 @@
|
||||
# Almacenamiento (SeaweedFS / S3)
|
||||
# Storage (SeaweedFS / S3)
|
||||
|
||||
## Qué es
|
||||
## What it is
|
||||
|
||||
**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`).
|
||||
**SeaweedFS** is the object store, S3-API compatible. It holds the uploaded originals and their derivatives (`.m4a` audio, thumbnails). Accessed via `AWSSDK.S3` from `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.
|
||||
It exposes:
|
||||
- **S3 API** (`:8333`): S3 operations (presigned URLs, put/get/list…).
|
||||
- **filer HTTP** (`:8888`): serves files by path, used by the **StreamingGateway**.
|
||||
- master (`:9333`) and volume server, internal only.
|
||||
|
||||
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).
|
||||
App bucket: **`webapi-storage`** (`ApplicationStorage:Bucket`). Important: the API and the Worker use **the same bucket** (the worker processes whatever the API uploads).
|
||||
|
||||
## Estructura de claves (layout)
|
||||
## Key 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).
|
||||
- `temp/{userId}/Tracks/{guid}.{ext}`: temporary uploads, the target of presigned PUTs, before the intent is consumed.
|
||||
- `uploads/{userId}/Tracks/OriginalPictures|OriginalAudios/...`: originals that have already been consumed.
|
||||
- `Tracks/SmallPictures|MediumPictures|LargePictures/...`: generated thumbnails.
|
||||
- `Tracks/ProcessedAudios/{AudioFolderName}/`: transcoded audio (`audio.m4a`, AAC with 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.
|
||||
Keys are always built with the `StorageKey.Combine(...)` helper (in `Application/Shared`), which joins segments with `/` and trims stray spaces and slashes. It's centralized to keep the logic consistent and avoid repeating it.
|
||||
|
||||
> En el filer, los objetos S3 viven bajo `/buckets/{bucket}/{key}` — de ahí que el gateway reescriba `/media/{key}` → `/buckets/webapi-storage/{key}`.
|
||||
> In the filer, S3 objects live under `/buckets/{bucket}/{key}`, which is why the gateway rewrites `/media/{key}` → `/buckets/webapi-storage/{key}`.
|
||||
|
||||
## URLs prefirmadas (presigned)
|
||||
## Presigned URLs
|
||||
|
||||
Para **no** hacer pasar ficheros grandes por la API, el cliente habla directo con S3 mediante URLs firmadas de corta vida:
|
||||
To **avoid** routing large files through the API, the client talks to S3 directly using short-lived signed URLs:
|
||||
|
||||
- **Subidas (PUT)**: `RequestTrackUploadUrls` / `RequestPlayListPictureUpload` generan presigned PUT (con `Content-Type` y `If-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).
|
||||
- **Uploads (PUT)**: `RequestTrackUploadUrls` / `RequestPlayListPictureUpload` generate presigned PUTs (with signed `Content-Type` and `If-None-Match: *`, ~120s expiry). The client uploads straight to S3.
|
||||
- **Images (GET)**: thumbnails are served via presigned GETs (one object = one 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.
|
||||
**Why presigned instead of proxying**: routing a ~100 MB audio file through the API would burn memory and bandwidth on the application servers and wouldn't scale well. A direct presigned PUT to S3 is the standard, correct pattern here.
|
||||
|
||||
### 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.
|
||||
### Upload intents and quota
|
||||
Every `upload-urls` call creates **UploadIntents** (reservations in the `Issued` state) and returns the URLs. They're **consumed** when the track/playlist is created. There's a per-user quota (`MaxActiveUploadIntentsPerUser`, byte limits…) to prevent abuse. The `UploadIntentExpirationJob` (in the Worker) marks expired intents and frees up quota. If that job isn't running, stale intents pile up and block new uploads.
|
||||
|
||||
## Qué es público y qué privado (despliegue)
|
||||
## What's public and what's private (deployment)
|
||||
|
||||
| Exponer a internet | Mantener privado |
|
||||
| Expose to the internet | Keep private |
|
||||
|---|---|
|
||||
| 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 |
|
||||
| S3 API `:8333` (behind TLS), needed for the client's presigned PUT/GET | filer `:8888` (internal network, gateway only) |
|
||||
| StreamingGateway `:8081` | master `:9333`, volume server, admin UI |
|
||||
|
||||
- **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 `:8333` debe ser alcanzable (pero inútil sin firma válida; es el modelo de cualquier bucket S3/MinIO).
|
||||
- En **producción**: `InfrastructureStorage:Address` debe 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).
|
||||
- **Audio** is served through the gateway, so SeaweedFS can stay private for that flow.
|
||||
- **Uploads and images** use presigned URLs, so the client talks to the S3 endpoint directly and `:8333` has to be reachable (though useless without a valid signature, this is the standard model for any S3/MinIO bucket).
|
||||
- In **production**: `InfrastructureStorage:Address` must be the **public S3 endpoint** (the signature is computed for that host). If the servers can't reach it internally, use split config: public host for signing, internal host for server-side operations.
|
||||
|
||||
**Regla**: el gateway y, como mucho, el `:8333` firmado son la única superficie pública; master/volume/filer/admin **nunca** se exponen.
|
||||
**Rule of thumb**: the gateway, and at most the signed `:8333`, are the only public surface. Master, volume, filer and admin are **never** exposed.
|
||||
|
||||
Reference in New Issue
Block a user