Docs

Almacenamiento y Media

Arquitectura del sistema de almacenamiento de Nvito con protocolo S3, buckets, URLs pre-firmadas, validación de archivos y proveedores por ambiente.

PublicadoMarzo 2026Equipo de desarrollo, arquitectos, stakeholders

Arquitectura General

El sistema de almacenamiento de Nvito utiliza el protocolo S3 como capa de abstracción, permitiendo cambiar entre proveedores (MinIO en desarrollo, Cloudflare R2 en producción) sin modificar el código de la aplicación.

Componentes Principales

ComponenteArchivoResponsabilidad
StorageServicemodules/storage/storage.service.tsOperaciones de almacenamiento (upload, download, delete, presign)
storage.config.tsconfig/storage.config.tsConfiguración de buckets, CDN, limites y MIME types
file-validation.utilcommon/utils/file-validation.util.tsValidación de archivos por magic bytes

S3 Client

El servicio inicializa un S3Client del AWS SDK v3 con configuración adaptativa:

this.s3Client = new S3Client({
  region: 'auto',
  endpoint: endpoint || `https://${accountId}.r2.cloudflarestorage.com`,
  credentials: { accessKeyId, secretAccessKey },
  forcePathStyle: isMinIO,  // MinIO requiere path-style, R2 usa virtual-hosted
});

Buckets de Almacenamiento

El sistema organiza los archivos en 4 buckets separados por proposito:

BucketVariablePropositoAcceso
nvito-uploadsR2_BUCKET_UPLOADSMedia subida por usuarios (fotos, videos, audio)Publico via CDN
nvito-assetsR2_BUCKET_ASSETSAssets del sistema (logos, iconos, fondos)Publico via CDN
nvito-privateR2_BUCKET_PRIVATEArchivos privados (contratos, documentos)Solo con URL firmada
nvito-templatesR2_BUCKET_TEMPLATESHTML compilado de templates e invitacionesPublico via CDN

Estructura de Archivos en nvito-uploads

Estructura de nvito-uploads

nvito-uploads/
└── org_{organizationId}/
└── events/
└── {eventId}/
├── media/
├── {timestamp}_{filename}.jpgFotos
├── {timestamp}_{filename}.mp4Videos
└── {timestamp}_{filename}.mp3Audio
└── invitations/
└── {invitationId}/
├── versions/
└── v{N}/
├── index.htmlHTML compilado
├── styles.cssCSS compilado
└── script.jsJS compilado (opcional)
└── live/
├── index.htmlVersion publicada
├── styles.css
└── script.js

Estructura de Archivos en nvito-templates

Estructura de nvito-templates

nvito-templates/
└── templates/
└── {templateId}/
├── template.htmlHTML del template base
└── assets/
└── {timestamp}_{file}Imagenes preview, fondos, etc.

URLs Pre-firmadas (Presigned URLs)

Para subir archivos, el frontend no envia el archivo al API. En su lugar, solicita una URL pre-firmada y sube directamente al almacenamiento, reduciendo la carga en el servidor.

Generación de Key

El key se construye con la siguiente estructura para evitar colisiónes:

org_{organizationId}/events/{eventId}/media/{timestamp}_{sanitizedFilename}

El filename se sanitiza eliminando caracteres especiales: filename.replace(/[^a-zA-Z0-9._-]/g, '_').

Expiracion

Las URLs pre-firmadas expiran después de 3600 segundos (1 hora) por defecto. El frontend recibe la fecha de expiración en expiresAt.

Validación Post-Upload

Después de que el frontend sube el archivo, el API descarga el archivo del bucket y valida:

  1. Magic bytes: Verifica que el contenido real coincide con el MIME type declarado
  2. Ejecutables: Rechaza archivos ejecutables detectados por analisis de bytes
  3. Acción en fallo: Si la validación falla, el archivo se elimina automáticamente del bucket

Tipos de Archivo Permitidos

MIME Types Aceptados

CategoríaFormatoMIME Type
ImagenesJPEGimage/jpeg
PNGimage/png
WebPimage/webp
GIFimage/gif
VideoMP4video/mp4
WebMvideo/webm
AudioMP3audio/mpeg, audio/mp3
WAVaudio/wav
M4Aaudio/m4a

Archivos de Invitación (generados por el sistema)

Además de los archivos subidos por usuarios, el sistema genera y almacena:

TipoContent-TypeCache-Control
HTML compiladotext/htmlpublic, max-age=3600 (1 hora)
CSS compiladotext/csspublic, max-age=31536000 (1 ano)
JS compiladoapplication/javascriptpublic, max-age=31536000 (1 ano)

Limites de Tamano por Plan

Los limites de tamano de archivo varian según el plan contratado por la organización:

PlanLimiteBytes
Free5 MB5,242,880
Essential25 MB26,214,400
Plus50 MB52,428,800
VIP100 MB104,857,600

Variables de Entorno para Limites

MAX_FILE_SIZE_FREE=5242880          # 5 MB
MAX_FILE_SIZE_ESSENTIAL=26214400    # 25 MB
MAX_FILE_SIZE_PLUS=52428800         # 50 MB
MAX_FILE_SIZE_VIP=104857600         # 100 MB

Estos valores son configurables por variable de entorno, permitiendo ajustarlos sin recompilar.

Proveedores de Almacenamiento

MinIO (Desarrollo Local)

MinIO es un servidor de almacenamiento de objetos compatible con S3, ideal para desarrollo local:

ParametroValor
Endpointhttp://localhost:9000
Consola Webhttp://localhost:9001
Regionauto
Path Styletrue (forcePathStyle)
Access KeyConfigurado en .env
Secret KeyConfigurado en .env

Detección automática: Si la variable R2_ENDPOINT está configurada (apuntando a MinIO), el servicio activa forcePathStyle: true automáticamente, ya que MinIO requiere path-style en lugar de virtual-hosted.

Cloudflare R2 (Producción)

Cloudflare R2 es el proveedor de almacenamiento para ambientes remotos:

ParametroValor
Endpointhttps://{R2_ACCOUNT_ID}.r2.cloudflarestorage.com
Regionauto
Path Stylefalse (virtual-hosted)
Account IDVariable R2_ACCOUNT_ID
Access KeyVariable R2_ACCESS_KEY_ID
Secret KeyVariable R2_SECRET_ACCESS_KEY

Tabla Comparativa

CaracteristicaMinIO (Local)Cloudflare R2 (Prod)
Endpointhttp://localhost:9000https://{id}.r2.cloudflarestorage.com
Path StyleSi (forcePathStyle: true)No (virtual-hosted)
URL públicahttp://localhost:9000/{bucket}/{key}https://cdn.nvito.mx/{key}
CostoGratisPay-per-use (egress gratis)
DisponibilidadSolo localGlobal
ConsolaMinIO Console (:9001)Cloudflare Dashboard

URLs de CDN

Construcción de URLs Publicas

El servicio construye URLs públicas de forma diferente según el proveedor:

MinIO (local):

http://localhost:9000/{bucket}/{key}
Ejemplo: http://localhost:9000/nvito-uploads/org_abc123/events/evt1/media/1707123456_foto.jpg

Cloudflare R2 (producción):

https://cdn.nvito.mx/{key}
Ejemplo: https://cdn.nvito.mx/org_abc123/events/evt1/media/1707123456_foto.jpg

Diferencia Clave

En MinIO, el nombre del bucket se incluye en la URL pública (/{bucket}/{key}). En R2, el bucket no aparece en la URL porque se configura a nivel de dominio personalizado (/{key}).

Cache-Control

Los archivos subidos por usuarios se sirven con cache agresivo:

Cache-Control: public, max-age=31536000    # 1 año (media de usuario)
Cache-Control: public, max-age=3600        # 1 hora (HTML de invitación)

URLs Privadas

Para archivos en el bucket nvito-private, se generan URLs firmadas de lectura con expiración configurable:

async getPrivateUrl(key: string, expiresIn: number = 3600): Promise<string>

Estás URLs son temporales y permiten descargar el archivo sin exponer credenciales.

Operaciones del StorageService

Resumen de Metodos

MetodoDescripción
uploadPublic()Subir archivo con buffer a bucket público
getUploadUrl()Generar URL pre-firmada para upload directo
exists()Verificar si un archivo existe
getObject()Descargar un archivo como Buffer
delete()Eliminar un archivo
deleteEventFiles()Eliminar todos los archivos de un evento
getPrivateUrl()Generar URL firmada para contenido privado
validateUploadedFileContent()Validar archivo subido por magic bytes
uploadTemplateHtml()Subir HTML de template al bucket de templates
uploadTemplateAsset()Subir asset de template (preview, fondo)
deleteTemplateFiles()Eliminar archivos de un template
uploadInvitationHtml()Subir HTML compilado de invitación (versionado)
uploadInvitationCss()Subir CSS compilado de invitación
uploadInvitationJs()Subir JS compilado de invitación
publishInvitationVersión()Copiar versión a directorio live/ (publicar)

Variables de Entorno

VariableDescripciónDefault
R2_ACCOUNT_IDAccount ID de Cloudflare R2-
R2_ACCESS_KEY_IDAccess Key del proveedor S3(requerido)
R2_SECRET_ACCESS_KEYSecret Key del proveedor S3(requerido)
R2_ENDPOINTEndpoint custom (MinIO: http://localhost:9000)- (usa R2)
R2_BUCKET_UPLOADSNombre del bucket de uploadsnvito-uploads
R2_BUCKET_ASSETSNombre del bucket de assetsnvito-assets
R2_BUCKET_PRIVATENombre del bucket privadonvito-private
R2_BUCKET_TEMPLATESNombre del bucket de templatesnvito-templates
CDN_URLURL base del CDNhttps://cdn.nvito.mx
CDN_ALLOWED_ORIGINSOrigenes permitidos (separados por coma)-
MAX_FILE_SIZE_FREELimite de archivo para plan Free5242880
MAX_FILE_SIZE_ESSENTIALLimite para plan Essential26214400
MAX_FILE_SIZE_PLUSLimite para plan Plus52428800
MAX_FILE_SIZE_VIPLimite para plan VIP104857600

Referencias

  • StorageService: src/modules/storage/storage.service.ts
  • Storage Config: src/config/storage.config.ts
  • File Validation: src/common/utils/file-validation.util.ts
Esta pagina fue util?