Docs

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

  1. Vision General
  2. Presigned URL Flow
  3. Confirmacion y Validacion
  4. Procesamiento de Imagenes
  5. Categorias de Media
  6. Limites por Plan
  7. Infraestructura de Storage
  8. Galeria Publica en Invitaciones
  9. Audio Guestbook
  10. Diagrama del Flujo de Upload
  11. 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

ConceptoDetalle
Validez de presigned URL15 minutos
Tamano maximo por archivoDepende del plan (5MB a 2GB)
Formatos imagenJPEG, PNG, WebP, AVIF, GIF
Formatos audioMP3, WAV, OGG, M4A, WebM
Formatos videoMP4, MOV, WebM
Thumbnails generados3 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:

CampoTipoDescripcion
eventIdUUIDEvento al que pertenece el media
categoryEnumCategoria del archivo (ver seccion 5)
fileNameStringNombre original del archivo
contentTypeStringMIME type declarado por el cliente
fileSizeNumberTamano en bytes

Paso 2: Validacion previa

Antes de generar la URL, MediaService valida:

  1. Permisos: El usuario tiene acceso al evento (via OrganizationResolverService)
  2. MIME type: Esta en la whitelist de tipos permitidos para la categoria
  3. Tamano: No excede el limite del plan de la organizacion
  4. 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-Type debe coincidir con el declarado, Content-Length dentro 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

CampoDescripcion
uploadUrlURL pre-firmada para PUT directo a S3
mediaIdUUID generado para el registro Media
keyRuta del archivo en el bucket
expiresAtMomento 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

  1. Verificar existencia: Confirmar que el archivo existe en S3 con el key esperado
  2. Verificar tamano real: Comparar Content-Length del objeto en S3 con el tamano declarado
  3. 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 typeMagic bytes esperados
image/jpegFF D8 FF
image/png89 50 4E 47 0D 0A 1A 0A
image/webp52 49 46 46 ... 57 45 42 50
image/gif47 49 46 38
audio/mpegFF FB o 49 44 33 (ID3)
video/mp400 00 00 ... 66 74 79 70 (ftyp)
  1. Actualizar registro: Marcar el Media como CONFIRMED, almacenar metadata (tamano real, dimensiones si es imagen)
  2. Disparar procesamiento: Encolar job de procesamiento en Bull queue

Registro Media

CampoTipoDescripcion
idUUIDIdentificador unico
eventIdUUIDEvento asociado
organizationIdUUIDOrganizacion propietaria
categoryEnumCategoria del media
originalFileNameStringNombre original del archivo
keyStringRuta en S3
bucketStringBucket de almacenamiento
contentTypeStringMIME type verificado
fileSizeNumberTamano en bytes
widthNumber (nullable)Ancho en pixeles (imagenes)
heightNumber (nullable)Alto en pixeles (imagenes)
durationNumber (nullable)Duracion en segundos (audio/video)
thumbnailsJSONURLs de thumbnails generados
statusEnumPENDING, CONFIRMED, PROCESSING, READY, ERROR
uploadedByUUIDUsuario que subio el archivo
createdAtDateTimeMomento de creacion

4. Procesamiento de Imagenes

Las imagenes confirmadas pasan por un pipeline de procesamiento usando la libreria sharp.

Pipeline

  1. Metadata extraction: Leer dimensiones (width, height), formato, espacio de color, y datos EXIF
  2. EXIF rotation: Aplicar rotacion automatica basada en la orientacion EXIF (comun en fotos de celular)
  3. Strip EXIF: Eliminar datos EXIF sensibles (ubicacion GPS, modelo de dispositivo) por privacidad
  4. Thumbnails: Generar 3 variantes redimensionadas:
VarianteTamano maxUso
thumb150x150px (crop center)Grilla de galeria, listas
medium400x400px (fit inside)Previews, cards
large800x800px (fit inside)Lightbox en invitaciones
  1. Formato WebP: Todas las variantes se generan en WebP para optimizar tamano
  2. Upload de variantes: Las 3 variantes se suben al bucket nvito-assets con keys derivados del original
  3. 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.

CategoriaBucketProcesamientoVisibilidad
GALLERYnvito-uploadsThumbnails + WebPPublica (invitacion)
HEROnvito-assetsThumbnails + WebPPublica (invitacion)
LOGOnvito-assetsResize 200px + WebPPublica (invitacion)
BACKGROUNDnvito-assetsResize 1920px + WebPPublica (invitacion)
AUDIOnvito-uploadsSolo validacionPublica (guestbook)
VIDEOnvito-uploadsSolo validacionPublica (galeria)

6. Limites por Plan

Cada organizacion tiene un plan que determina los limites de almacenamiento:

PlanLimite por archivoCuota totalArchivos max
FREE5 MB100 MB20
ESSENTIAL50 MB2 GB200
PLUS200 MB10 GB1,000
VIP500 MB50 GB5,000
PREMIUM2 GB200 GBIlimitado

Verificacion de cuota

Antes de generar una presigned URL, el sistema calcula el almacenamiento usado:

  1. Suma fileSize de todos los Media con status != ERROR de la organizacion
  2. Compara con la cuota del plan
  3. Si la suma + el nuevo archivo excede la cuota, retorna 413 Payload Too Large con detalle de uso

7. Infraestructura de Storage

4 Buckets

BucketPropositoAcceso
nvito-uploadsArchivos subidos por usuarios (galeria, audio, video)Privado, acceso via presigned URL
nvito-assetsImagenes procesadas (hero, logo, background, thumbnails)Publico via CDN
nvito-privateArchivos internos (exports, reportes)Privado, solo API
nvito-templatesHTML compilado de invitacionesPublico via CDN

CDN y Storage por ambiente

AmbienteStorageCDN
DesarrolloMinIO (local, puerto 9000)Directo desde MinIO
ProduccionCloudflare R2Cloudflare 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

  1. La invitacion HTML incluye una seccion gallery con URLs de thumbnails medium (400px)
  2. Al hacer clic en una foto, se abre el lightbox con la variante large (800px)
  3. 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:

  1. Invitado selecciona o toma una foto desde su dispositivo
  2. La app solicita presigned URL y sube directo a S3
  3. Confirma el upload y la foto entra en cola de procesamiento
  4. Al estar lista, aparece automaticamente en la galeria del evento
  5. 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

PlataformaTecnologiaFormato nativo
App nativaexpo-av (Audio.Recording)M4A (iOS), OGG (Android)
PWAMediaRecorder APIWebM (Chrome), OGG (Firefox)

Flujo

  1. Invitado presiona "Grabar mensaje" en la seccion de audio guestbook
  2. La app solicita permiso de microfono
  3. Graba hasta 60 segundos (limite configurable)
  4. Muestra preview con boton de reproduccion
  5. Invitado confirma y el audio se sube via presigned URL flow
  6. El audio aparece en la lista de mensajes del evento
  7. El anfitrion puede reproducir, descargar o eliminar mensajes

Metadata de audio

CampoDescripcion
durationDuracion en segundos
guestIdInvitado que grabo el mensaje
guestNameNombre para mostrar en la lista
recordedAtMomento de la grabacion

10. Diagrama del Flujo de Upload

11. Archivos Clave

nvito-api (Backend)

ArchivoResponsabilidad
media.service.tsOrquestador: get-upload-url, confirm-upload, listado
storage.service.tsAbstraccion S3: presigned URLs, head, get, delete
image-processor.service.tsPipeline sharp: resize, thumbnails, WebP, metadata
media.controller.tsEndpoints REST para media
media-processing.processor.tsBull processor para procesamiento asincrono

nvito-admin (Panel de Administracion)

ArchivoResponsabilidad
lib/api/services/media.service.tsServicio API para upload desde admin
lib/actions/media-actions.tsServer actions: getUploadUrlAction, confirmUploadAction
components/media/upload-dialog.tsxDialog de upload con drag & drop
components/media/gallery-manager.tsxGestion de galeria del evento

nvito-client (App Nativa)

ArchivoResponsabilidad
src/api/services/media.service.tsServicio API para upload movil
src/hooks/queries/use-media.tsHook de React Query para media
app/(app)/(host)/gallery.tsxPantalla de galeria (anfitrion)
app/(app)/(guest)/gallery-guest.tsxPantalla de galeria (invitado)
app/(app)/(guest)/interact.tsxAudio guestbook del invitado

nvito-pwa (Progressive Web App)

ArchivoResponsabilidad
src/lib/api/services/media.service.tsServicio API via BFF proxy
src/hooks/use-media-recorder.tsWrapper de MediaRecorder API
src/app/(app)/(host)/gallery/page.tsxGaleria del anfitrion
src/app/(app)/(guest)/gallery-guest/page.tsxGaleria del invitado
Esta pagina fue util?