Flujo de Media y Galeria
Upload seguro con presigned URLs, procesamiento de imagenes con sharp, almacenamiento en S3/R2, y distribucion global via CDN. Incluye galeria publica en invitaciones y audio guestbook para mensajes de voz.
Tabla de Contenidos
- Vision General
- Presigned URL Flow
- Confirmacion y Validacion
- Procesamiento de Imagenes
- Categorias de Media
- Limites por Plan
- Infraestructura de Storage
- Galeria Publica en Invitaciones
- Audio Guestbook
- Diagrama del Flujo de Upload
- Archivos Clave
1. Vision General
El sistema de media de Nvito maneja todos los archivos subidos por usuarios: fotos de la galeria del evento, imagenes hero de invitaciones, logos de organizacion, fondos de seccion, audio del guestbook, y videos del evento.
El diseno sigue el principio de upload directo a S3: el cliente nunca envia el archivo al servidor de Nvito. En su lugar, solicita una URL pre-firmada (presigned URL), sube el archivo directamente al bucket de S3, y luego confirma la operacion. Esto elimina la carga de procesamiento del API y permite manejar archivos grandes sin timeout.
Numeros clave
| Concepto | Detalle |
|---|---|
| Validez de presigned URL | 15 minutos |
| Tamano maximo por archivo | Depende del plan (5MB a 2GB) |
| Formatos imagen | JPEG, PNG, WebP, AVIF, GIF |
| Formatos audio | MP3, WAV, OGG, M4A, WebM |
| Formatos video | MP4, MOV, WebM |
| Thumbnails generados | 3 tamaños (150px, 400px, 800px) |
2. Presigned URL Flow
El upload comienza cuando el cliente solicita permiso para subir un archivo. El servidor no recibe el archivo, solo genera una URL temporal con permisos de escritura.
Paso 1: Solicitar URL
El cliente envia POST /v1/media/get-upload-url con:
| Campo | Tipo | Descripcion |
|---|---|---|
eventId | UUID | Evento al que pertenece el media |
category | Enum | Categoria del archivo (ver seccion 5) |
fileName | String | Nombre original del archivo |
contentType | String | MIME type declarado por el cliente |
fileSize | Number | Tamano en bytes |
Paso 2: Validacion previa
Antes de generar la URL, MediaService valida:
- Permisos: El usuario tiene acceso al evento (via
OrganizationResolverService) - MIME type: Esta en la whitelist de tipos permitidos para la categoria
- Tamano: No excede el limite del plan de la organizacion
- Cuota: La organizacion no ha excedido su cuota total de almacenamiento
Paso 3: Generar presigned URL
StorageService.generatePresignedUrl() genera una URL con:
- Metodo: PUT
- Bucket: Segun la categoria (ver seccion 7)
- Key:
{organizationId}/{eventId}/{category}/{uuid}.{ext} - Expiracion: 15 minutos
- Condiciones:
Content-Typedebe coincidir con el declarado,Content-Lengthdentro del rango
Paso 4: Upload directo
El cliente ejecuta un PUT directo contra la presigned URL de S3 con el archivo como body. Este request no pasa por nvito-api.
Respuesta al cliente
| Campo | Descripcion |
|---|---|
uploadUrl | URL pre-firmada para PUT directo a S3 |
mediaId | UUID generado para el registro Media |
key | Ruta del archivo en el bucket |
expiresAt | Momento en que la URL expira |
3. Confirmacion y Validacion
Despues de subir el archivo a S3, el cliente debe confirmar la operacion. Esta confirmacion dispara la validacion real del archivo.
Endpoint de confirmacion
POST /v1/media/confirm-upload con { mediaId }
Pipeline de validacion
- Verificar existencia: Confirmar que el archivo existe en S3 con el key esperado
- Verificar tamano real: Comparar
Content-Lengthdel objeto en S3 con el tamano declarado - Magic bytes check: Leer los primeros bytes del archivo y verificar que coincidan con el MIME type declarado. Esto previene ataques donde se renombra un ejecutable como
.jpg
| MIME type | Magic bytes esperados |
|---|---|
image/jpeg | FF D8 FF |
image/png | 89 50 4E 47 0D 0A 1A 0A |
image/webp | 52 49 46 46 ... 57 45 42 50 |
image/gif | 47 49 46 38 |
audio/mpeg | FF FB o 49 44 33 (ID3) |
video/mp4 | 00 00 00 ... 66 74 79 70 (ftyp) |
- Actualizar registro: Marcar el
MediacomoCONFIRMED, almacenar metadata (tamano real, dimensiones si es imagen) - Disparar procesamiento: Encolar job de procesamiento en Bull queue
Registro Media
| Campo | Tipo | Descripcion |
|---|---|---|
id | UUID | Identificador unico |
eventId | UUID | Evento asociado |
organizationId | UUID | Organizacion propietaria |
category | Enum | Categoria del media |
originalFileName | String | Nombre original del archivo |
key | String | Ruta en S3 |
bucket | String | Bucket de almacenamiento |
contentType | String | MIME type verificado |
fileSize | Number | Tamano en bytes |
width | Number (nullable) | Ancho en pixeles (imagenes) |
height | Number (nullable) | Alto en pixeles (imagenes) |
duration | Number (nullable) | Duracion en segundos (audio/video) |
thumbnails | JSON | URLs de thumbnails generados |
status | Enum | PENDING, CONFIRMED, PROCESSING, READY, ERROR |
uploadedBy | UUID | Usuario que subio el archivo |
createdAt | DateTime | Momento de creacion |
4. Procesamiento de Imagenes
Las imagenes confirmadas pasan por un pipeline de procesamiento usando la libreria sharp.
Pipeline
- Metadata extraction: Leer dimensiones (width, height), formato, espacio de color, y datos EXIF
- EXIF rotation: Aplicar rotacion automatica basada en la orientacion EXIF (comun en fotos de celular)
- Strip EXIF: Eliminar datos EXIF sensibles (ubicacion GPS, modelo de dispositivo) por privacidad
- Thumbnails: Generar 3 variantes redimensionadas:
| Variante | Tamano max | Uso |
|---|---|---|
thumb | 150x150px (crop center) | Grilla de galeria, listas |
medium | 400x400px (fit inside) | Previews, cards |
large | 800x800px (fit inside) | Lightbox en invitaciones |
- Formato WebP: Todas las variantes se generan en WebP para optimizar tamano
- Upload de variantes: Las 3 variantes se suben al bucket
nvito-assetscon keys derivados del original - Actualizar registro: Guardar URLs de thumbnails en el campo JSON del Media
Procesamiento asincrono
El procesamiento corre en una Bull queue (media-processing) con:
- Concurrencia: 2 jobs simultaneos por worker
- Timeout: 60 segundos por job
- Reintentos: 3 intentos con backoff exponencial
- Dead letter: Jobs fallidos se mueven a DLQ para revision manual
5. Categorias de Media
Cada archivo subido pertenece a una categoria que determina su bucket, procesamiento y visibilidad.
| Categoria | Bucket | Procesamiento | Visibilidad |
|---|---|---|---|
GALLERY | nvito-uploads | Thumbnails + WebP | Publica (invitacion) |
HERO | nvito-assets | Thumbnails + WebP | Publica (invitacion) |
LOGO | nvito-assets | Resize 200px + WebP | Publica (invitacion) |
BACKGROUND | nvito-assets | Resize 1920px + WebP | Publica (invitacion) |
AUDIO | nvito-uploads | Solo validacion | Publica (guestbook) |
VIDEO | nvito-uploads | Solo validacion | Publica (galeria) |
6. Limites por Plan
Cada organizacion tiene un plan que determina los limites de almacenamiento:
| Plan | Limite por archivo | Cuota total | Archivos max |
|---|---|---|---|
| FREE | 5 MB | 100 MB | 20 |
| ESSENTIAL | 50 MB | 2 GB | 200 |
| PLUS | 200 MB | 10 GB | 1,000 |
| VIP | 500 MB | 50 GB | 5,000 |
| PREMIUM | 2 GB | 200 GB | Ilimitado |
Verificacion de cuota
Antes de generar una presigned URL, el sistema calcula el almacenamiento usado:
- Suma
fileSizede todos losMediaconstatus != ERRORde la organizacion - Compara con la cuota del plan
- Si la suma + el nuevo archivo excede la cuota, retorna
413 Payload Too Largecon detalle de uso
7. Infraestructura de Storage
4 Buckets
| Bucket | Proposito | Acceso |
|---|---|---|
nvito-uploads | Archivos subidos por usuarios (galeria, audio, video) | Privado, acceso via presigned URL |
nvito-assets | Imagenes procesadas (hero, logo, background, thumbnails) | Publico via CDN |
nvito-private | Archivos internos (exports, reportes) | Privado, solo API |
nvito-templates | HTML compilado de invitaciones | Publico via CDN |
CDN y Storage por ambiente
| Ambiente | Storage | CDN |
|---|---|---|
| Desarrollo | MinIO (local, puerto 9000) | Directo desde MinIO |
| Produccion | Cloudflare R2 | Cloudflare CDN (cdn.nvito.mx) |
Estructura de keys
Los archivos siguen una estructura de directorios logica:
{bucket}/
{organizationId}/
{eventId}/
gallery/
{uuid}.jpg (original)
{uuid}_thumb.webp (150px)
{uuid}_medium.webp (400px)
{uuid}_large.webp (800px)
hero/
{uuid}.jpg
audio/
{uuid}.mp3
video/
{uuid}.mp4
8. Galeria Publica en Invitaciones
La seccion de galeria en las invitaciones publicas muestra las fotos del evento en una grilla responsive con lightbox.
Flujo de visualizacion
- La invitacion HTML incluye una seccion
gallerycon URLs de thumbnailsmedium(400px) - Al hacer clic en una foto, se abre el lightbox con la variante
large(800px) - Las imagenes se cargan desde CDN (
cdn.nvito.mx) con cache headers agresivos
Galeria colaborativa (dia del evento)
Los invitados pueden subir fotos desde la app movil o PWA:
- Invitado selecciona o toma una foto desde su dispositivo
- La app solicita presigned URL y sube directo a S3
- Confirma el upload y la foto entra en cola de procesamiento
- Al estar lista, aparece automaticamente en la galeria del evento
- El anfitrion puede moderar: aprobar o rechazar fotos desde el admin o la app
9. Audio Guestbook
El audio guestbook permite a los invitados grabar mensajes de voz para los anfitriones.
Grabacion
| Plataforma | Tecnologia | Formato nativo |
|---|---|---|
| App nativa | expo-av (Audio.Recording) | M4A (iOS), OGG (Android) |
| PWA | MediaRecorder API | WebM (Chrome), OGG (Firefox) |
Flujo
- Invitado presiona "Grabar mensaje" en la seccion de audio guestbook
- La app solicita permiso de microfono
- Graba hasta 60 segundos (limite configurable)
- Muestra preview con boton de reproduccion
- Invitado confirma y el audio se sube via presigned URL flow
- El audio aparece en la lista de mensajes del evento
- El anfitrion puede reproducir, descargar o eliminar mensajes
Metadata de audio
| Campo | Descripcion |
|---|---|
duration | Duracion en segundos |
guestId | Invitado que grabo el mensaje |
guestName | Nombre para mostrar en la lista |
recordedAt | Momento de la grabacion |
10. Diagrama del Flujo de Upload
11. Archivos Clave
nvito-api (Backend)
| Archivo | Responsabilidad |
|---|---|
media.service.ts | Orquestador: get-upload-url, confirm-upload, listado |
storage.service.ts | Abstraccion S3: presigned URLs, head, get, delete |
image-processor.service.ts | Pipeline sharp: resize, thumbnails, WebP, metadata |
media.controller.ts | Endpoints REST para media |
media-processing.processor.ts | Bull processor para procesamiento asincrono |
nvito-admin (Panel de Administracion)
| Archivo | Responsabilidad |
|---|---|
lib/api/services/media.service.ts | Servicio API para upload desde admin |
lib/actions/media-actions.ts | Server actions: getUploadUrlAction, confirmUploadAction |
components/media/upload-dialog.tsx | Dialog de upload con drag & drop |
components/media/gallery-manager.tsx | Gestion de galeria del evento |
nvito-client (App Nativa)
| Archivo | Responsabilidad |
|---|---|
src/api/services/media.service.ts | Servicio API para upload movil |
src/hooks/queries/use-media.ts | Hook de React Query para media |
app/(app)/(host)/gallery.tsx | Pantalla de galeria (anfitrion) |
app/(app)/(guest)/gallery-guest.tsx | Pantalla de galeria (invitado) |
app/(app)/(guest)/interact.tsx | Audio guestbook del invitado |
nvito-pwa (Progressive Web App)
| Archivo | Responsabilidad |
|---|---|
src/lib/api/services/media.service.ts | Servicio API via BFF proxy |
src/hooks/use-media-recorder.ts | Wrapper de MediaRecorder API |
src/app/(app)/(host)/gallery/page.tsx | Galeria del anfitrion |
src/app/(app)/(guest)/gallery-guest/page.tsx | Galeria del invitado |