Sharely — Documentación Técnica

Versión: 1.0.0 · Licencia: MIT · Node.js: ≥ 18

Índice

1. Descripción del proyecto

2. Arquitectura

3. Stack tecnológico

4. Estructura de directorios

5. Configuración y variables de entorno

6. Modelos de datos

7. API REST

8. API WebSocket

9. Rutas de servicio de archivos

10. Autenticación y seguridad

11. Sistema de carga

12. Generación de miniaturas

13. Correo electrónico y SMTP

14. Internacionalización

15. Frontend (React SPA)

16. Panel de administración

17. RGPD / Privacidad

18. Tareas en segundo plano

19. Despliegue

20. Entorno de desarrollo

21. Migraciones y scripts

22. Pruebas end-to-end


1. Descripción del proyecto

Sharely es una plataforma de intercambio de archivos autoalojada con una interfaz web limpia, integración con ShareX y acceso mediante API. Los usuarios suben capturas de pantalla, archivos y contenido multimedia, y los comparten al instante mediante enlaces cortos.

Funcionalidades principales

FuncionalidadDescripción
Carga webArrastrar y soltar, hasta 500 archivos simultáneamente
Carga por partesArchivos de hasta 2 GB mediante carga multi-parte paralela
Integración ShareXArchivo de configuración .sxcu descargable con un clic
Carga por APIAutenticación por token Bearer, compatible con curl/wget
Visor de archivosZoom en imágenes, transmisión de vídeo/audio (HTTP Range), PDF en línea, código con resaltado de sintaxis
Modos de incrustación*embed* (HTML OG/Twitter Card) o *raw* (redirección directa)
MiniaturasVistas previas JPEG automáticas para vídeos (ffmpeg) y PDFs (ghostscript)
ColeccionesAgrupaciones de archivos con contraseña y fecha de expiración opcionales
Enlaces de comparticiónEnlaces por archivo con contraseña, expiración y límite de descargas
Interfaz en tiempo realActualizaciones en vivo por WebSocket (carga, eliminación, contador de visitas, estadísticas admin)
Multilingüe8 idiomas: EN, DE, FR, ES, IT, PT, JA, ZH
Panel de administraciónEstadísticas, gestión de usuarios, gestión de archivos, registro de auditoría (exportación CSV)
Cumplimiento RGPDFuncionalidades de privacidad conforme al RGPD (Art. 17, 20, 32, etc.)
Importación XBackBoneMigración de instalaciones XBackBone existentes
Listo para Dockerdocker compose up -d inicia el entorno completo

2. Arquitectura

┌─────────────────────────────────────────────────────────────┐
│                        Browser / Client                      │
│            React 18 SPA  ·  Vite  ·  Tailwind CSS           │
│   ┌──────────────────────────────────────────────────────┐   │
│   │  HTTP REST (/api/*)            WebSocket (/ws)       │   │
│   └──────────────────────────────────────────────────────┘   │
└───────────────────────────┬────────────────────┬─────────────┘
                            │ HTTP               │ WS
┌───────────────────────────▼────────────────────▼─────────────┐
│                    Express.js (app.js)                        │
│                                                               │
│  ┌──────────────┐  ┌───────────┐  ┌──────────┐  ┌────────┐  │
│  │  /api/auth   │  │  /api/*   │  │   /f/*   │  │  /s/*  │  │
│  │  /api/install│  │  routes   │  │  files   │  │ shares │  │
│  └──────────────┘  └───────────┘  └──────────┘  └────────┘  │
│                                                               │
│  Middleware: session · rate-limit · CSRF · CSP · auth        │
│                                                               │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌────────────┐  │
│  │  multer  │  │  ws.js   │  │  mailer  │  │ retention  │  │
│  │  upload  │  │ WebSocket│  │ nodemailer│  │  cleanup   │  │
│  └──────────┘  └──────────┘  └──────────┘  └────────────┘  │
└───────────────────────────┬───────────────────────────────────┘
                            │
┌───────────────────────────▼───────────────────────────────────┐
│                      MongoDB (Mongoose)                        │
│  User · File · Collection · ShareLink · SiteSettings          │
│  AuditLog                                                      │
└───────────────────────────────────────────────────────────────┘
                            │
              ┌─────────────▼───────────────┐
              │     Dateisystem (uploads/)   │
              │  {folderName}/  ·  .chunks/  │
              │  .thumbnails/   ·  .avatars/ │
              └─────────────────────────────┘

Flujo de datos: Carga estándar

Browser → POST /api/web-upload (multipart/form-data)
       → multer: Datei in uploads/{folderName}/
       → File.createUnique() → MongoDB
       → generateThumbnail() (async, non-blocking)
       → logAudit()
       → broadcast('file:uploaded') via WebSocket
       ← JSON: { files: [...] }

Flujo de datos: Carga ShareX

ShareX → POST /upload (token im Formular-Body)
       → multer: Datei temporär in uploads/
       → requireApiKey: Token-Lookup → User
       → fs.renameSync → uploads/{folderName}/
       → File.create() → MongoDB
       ← JSON: { url, delete_url }

3. Stack tecnológico

CapaTecnologíaVersión
RuntimeNode.js≥ 18
Framework backendExpress.js4.x
Base de datosMongoDB + MongooseMongo 7, Mongoose 8.x
Sesionesexpress-session + connect-mongo
Tiempo realWebSocket (ws)8.x
Carga de archivosMulter1.x
Correo electrónicoNodemailer8.x
Hash de contraseñasbcryptjs2.x (12 rondas)
Hash de claves APISHA-256 (Node Crypto)
Limitación de tasaexpress-rate-limit8.x
Importación XBackBonesql.js1.x
Framework frontendReact 1818.3.x
Enrutamiento (frontend)React Router v66.x
Herramienta de compilaciónVite6.x
EstilosTailwind CSS + Radix UI3.x
Componentes UIshadcn/ui (Radix primitives)
IconosFontAwesome6.x
i18ni18next + react-i18next
Resaltado de sintaxishighlight.js11.x
ContenedoresDocker + Docker Compose
PruebasPlaywright (E2E)1.60.x

4. Estructura de directorios

sharely/
├── app.js                          # Punto de entrada Express, secuencia de inicio
├── package.json
├── .env.example                    # Plantilla para todas las variables de entorno
├── Dockerfile
├── docker-compose.yml
│
├── src/
│   ├── config/
│   │   └── db.js                   # Conexión MongoDB (Mongoose)
│   ├── middleware/
│   │   ├── auth.js                 # requireLogin / requireAdmin / requireApiKey
│   │   └── upload.js               # Configuración Multer, lista de bloqueo
│   ├── models/
│   │   ├── AuditLog.js             # Eventos de auditoría (TTL 90 días)
│   │   ├── Collection.js           # Colecciones de archivos
│   │   ├── File.js                 # Metadatos de archivos
│   │   ├── ShareLink.js            # Enlaces de compartición por archivo
│   │   ├── SiteSettings.js         # Singleton: configuración del operador
│   │   └── User.js                 # Cuentas de usuario + claves API
│   ├── routes/
│   │   ├── api.js                  # API principal (carga, galería, admin, ...)
│   │   ├── auth.js                 # Login / Registro / Restablecimiento de contraseña
│   │   ├── files.js                # Servicio de archivos, embeds OG, solicitudes de rango
│   │   ├── import.js               # Migración XBackBone
│   │   ├── install.js              # Endpoint de instalación inicial
│   │   └── shares.js               # Servicio de archivos por enlace de compartición
│   ├── jobs/
│   │   └── retentionCleanup.js     # Eliminación diaria de archivos expirados
│   ├── migrations/
│   │   ├── migrateApiKeyHashes.js  # Única vez: textos en claro → hashes SHA-256
│   │   └── migrateUserFolders.js   # Única vez: mover archivos a carpetas de usuario
│   ├── utils/
│   │   ├── audit.js                # Función auxiliar logAudit()
│   │   ├── generateThumbnail.js    # Integración ffmpeg / ghostscript
│   │   ├── mailer.js               # Wrapper Nodemailer + plantillas de email i18n
│   │   └── sanitizeFilename.js     # Saneamiento de nombre de archivo
│   └── ws.js                       # Servidor WebSocket + despachador de acciones
│
├── client/                         # Frontend React
│   ├── index.html
│   ├── package.json
│   ├── vite.config.js
│   ├── tailwind.config.js
│   └── src/
│       ├── main.jsx                # Punto de entrada React
│       ├── App.jsx                 # Configuración del enrutador
│       ├── index.css               # Estilos globales
│       ├── context/
│       │   └── AuthContext.jsx     # Estado de autenticación global
│       ├── hooks/
│       │   ├── use-toast.js        # Hook de notificaciones toast
│       │   └── useWebSocket.js     # Conexión WS + manejadores de eventos
│       ├── components/
│       │   ├── Layout.jsx          # Shell de la aplicación (barra de navegación, sidebar)
│       │   ├── ProtectedRoute.jsx  # Guardia de autenticación
│       │   ├── ShareLinkDialog.jsx # Diálogo de creación de enlace de compartición
│       │   ├── AddToCollectionDialog.jsx
│       │   ├── CookieBanner.jsx
│       │   ├── LanguageSelector.jsx
│       │   ├── RequireEmailDialog.jsx
│       │   ├── UserAvatar.jsx
│       │   └── ui/                 # Componentes base shadcn/ui
│       ├── pages/
│       │   ├── Upload.jsx          # Página de carga
│       │   ├── Gallery.jsx         # Galería de archivos
│       │   ├── FileView.jsx        # Vista detallada de archivo
│       │   ├── Collections.jsx     # Vista general de colecciones
│       │   ├── CollectionView.jsx  # Colección individual
│       │   ├── ShareView.jsx       # Página pública de enlace de compartición
│       │   ├── Settings.jsx        # Configuración de usuario
│       │   ├── Login.jsx
│       │   ├── Register.jsx
│       │   ├── ForgotPassword.jsx
│       │   ├── ResetPassword.jsx
│       │   ├── Install.jsx         # Instalación inicial
│       │   ├── PrivacyPolicy.jsx
│       │   ├── TermsOfService.jsx
│       │   └── admin/
│       │       ├── Dashboard.jsx   # Página de inicio admin
│       │       ├── Users.jsx       # Gestión de usuarios
│       │       ├── Files.jsx       # Gestión de archivos
│       │       ├── AuditLog.jsx    # Vista del registro de auditoría
│       │       ├── SiteSettings.jsx# Configuración del operador
│       │       └── Import.jsx      # Importación XBackBone
│       ├── i18n/
│       │   ├── index.js            # Configuración i18next
│       │   └── locales/
│       │       ├── de.json
│       │       ├── en.json
│       │       ├── es.json
│       │       ├── fr.json
│       │       ├── it.json
│       │       ├── ja.json
│       │       ├── pt.json
│       │       └── zh.json
│       └── lib/
│           └── utils.js            # Utilidad Tailwind (cn())
│
├── scripts/
│   ├── setup-db.js
│   ├── mongo-init.js               # Script de inicialización MongoDB
│   ├── migrate-uploads-to-user-folders.js
│   └── generate-missing-thumbnails.js
│
├── e2e/                            # Pruebas Playwright
│   ├── admin.spec.js
│   ├── bulk-actions-fixes.spec.js
│   ├── gallery.spec.js
│   ├── sharelink.spec.js
│   ├── tags.spec.js
│   ├── upload.spec.js
│   ├── helpers.js
│   └── global-setup.js
│
├── uploads/                        # Archivos subidos (runtime)
│   ├── .thumbnails/
│   ├── .avatars/
│   └── .chunks/                    # Fragmentos temporales
│
└── docs/assets/                    # Capturas de pantalla y logotipo

5. Configuración y variables de entorno

Todas las variables se cargan desde .env (mediante dotenv). El archivo .env.example contiene la plantilla completa.

Campos obligatorios

VariableDescripción
SESSION_SECRETSecreto para el cifrado de sesiones — cadena aleatoria larga, p. ej. openssl rand -hex 32
MONGO_ROOT_PASSWORDContraseña root de MongoDB (requerida solo para Docker Compose)
MONGO_APP_PASSWORDContraseña del usuario de aplicación de MongoDB

Todas las variables de entorno

VariablePor defectoDescripción
PORT3000Puerto TCP del servidor HTTP
MONGODB_URI_(construida desde Docker Compose)_URI de conexión MongoDB completa
MONGO_ROOT_PASSWORDContraseña root de MongoDB
MONGO_APP_USERappuserNombre de usuario de aplicación MongoDB
MONGO_APP_PASSWORDContraseña del usuario de aplicación MongoDB
MONGO_DB_NAMEsharelyNombre de la base de datos MongoDB
SESSION_SECRETObligatorio — secreto de cifrado de sesiones
BASE_URLhttp://localhost:3000URL base pública para los enlaces de compartición generados (sin / final)
SITE_NAMEsharelyNombre del sitio en los embeds Open Graph
MAX_FILE_SIZE_MB100Tamaño máximo de archivo para cargas estándar en MB (cargas por partes hasta 2 GB independientemente)
ALLOW_REGISTRATIONtruefalse desactiva el registro público
SMTP_HOSTNombre de host del servidor SMTP; dejar vacío para desactivar las funciones de email
SMTP_PORT587Puerto SMTP
SMTP_SECUREfalsetrue para TLS implícito (puerto 465), false para STARTTLS
SMTP_USERNombre de usuario SMTP
SMTP_PASSContraseña SMTP
SMTP_FROM_(SMTP_USER)_Dirección de remitente en los correos salientes
UPLOAD_DIR./uploadsRuta absoluta al directorio de carga
NODE_ENVproduction activa las cookies seguras

6. Modelos de datos

User (src/models/User.js)

{
  username:                    String (3–32, único, alfanumérico + _-)
  password:                    String (bcrypt, 12 rondas)
  role:                        'admin' | 'user'
  apiKey:                      String (legado, vacío tras migración)
  apiKeyHash:                  String (SHA-256, único, sparse)
  apiKeyPrefix:                String (primeros 8 caracteres del texto en claro)
  folderName:                  String (único, sparse, máx. 64)
  avatarExt:                   String | null (.jpg/.png/.gif/.webp)
  embedMode:                   'embed' | 'raw'
  isActive:                    Boolean
  email:                       String (minúsculas, único, sparse)
  emailVerified:               Boolean
  emailVerificationToken:      String | null (hash SHA-256 del texto en claro)
  emailVerificationExpires:    Date | null
  passwordResetToken:          String | null (hash SHA-256)
  passwordResetExpires:        Date | null
  language:                    'en'|'de'|'fr'|'es'|'it'|'pt'|'ja'|'zh'
  predefinedTags:              [String] (máx. 100 etiquetas × 50 caracteres)
  createdAt:                   Date
}

Métodos importantes:

File (src/models/File.js)

{
  shortId:      String (8 caracteres hexadecimales: 6 timestamp + 2 aleatorios, único)
  deleteToken:  String (32 caracteres hexadecimales, único)
  originalName: String (saneado)
  storedName:   String (ruta relativa: "folderName/8hex.ext")
  mimeType:     String
  size:         Number (bytes)
  uploader:     ObjectId → User
  views:        Number
  tags:         [String] (máx. 20 × 50 caracteres)
  createdAt:    Date
}

Propiedades calculadas:

Algoritmo Short ID:

shortId = hex(seconds_since_2024-01-01, 6 chars) + randomBytes(2).hex()

Collection (src/models/Collection.js)

{
  shortId:     String (8 Hex, único)
  name:        String (máx. 100)
  description: String (máx. 500)
  owner:       ObjectId → User
  files:       [ObjectId → File]
  password:    String | null (bcrypt)
  expiresAt:   Date | null
  createdAt:   Date
}
Las colecciones expiradas son eliminadas automáticamente por el índice TTL de MongoDB 7 días después de su expiración.
{
  token:         String (32 Hex, único)
  file:          ObjectId → File
  createdBy:     ObjectId → User
  label:         String (máx. 100)
  password:      String | null (bcrypt)
  expiresAt:     Date | null
  downloadLimit: Number (-1 = ilimitado)
  downloadCount: Number
  createdAt:     Date
}
Como las colecciones: período de gracia de 7 días después de la expiración mediante índice TTL.

SiteSettings (src/models/SiteSettings.js)

Documento singleton (_id: 'singleton'):

{
  operatorName:        String
  operatorAddress:     String
  operatorEmail:       String
  cloudflareAnalytics: Boolean
  fileRetentionDays:   Number (0 = desactivado)
  encryptionAtRest:    Boolean
  sessionDurationDays: Number (por defecto: 7)
  allowRegistration:   Boolean
}

AuditLog (src/models/AuditLog.js)

{
  timestamp: Date (índice TTL: 90 días)
  userId:    ObjectId → User | null
  username:  String | null
  action:    String
  ip:        String | null
  meta:      Mixed (metadatos específicos de la acción)
}

7. API REST

URL base: /api

Todos los endpoints JSON devuelven Content-Type: application/json. Errores: { "error": "mensaje" }.


Autenticación (/api/auth)

MétodoRutaAuthDescripción
GET/meSesiónUsuario actualmente conectado
POST/loginIniciar sesión (limitado: 10/15min)
POST/registerRegistrarse (limitado, el primer usuario se convierte en admin)
POST/logoutCerrar sesión
GET/smtp-enabledComprobar si SMTP está configurado
GET/verify-email?token=Verificar dirección de email
GET/verify-reset-token?token=Validar token de restablecimiento
POST/forgot-passwordEnviar email de restablecimiento de contraseña (limitado: 5/h)
POST/reset-passwordEstablecer nueva contraseña

Petición de inicio de sesión:

POST /api/auth/login
{ "username": "max", "password": "meinPasswort123" }

Respuesta de inicio de sesión:

{
  "user": {
    "id": "...", "username": "max", "role": "user",
    "avatarUrl": null, "email": "[email protected]",
    "emailVerified": true, "language": "de"
  }
}

Carga de archivos

MétodoRutaAuthDescripción
POST/uploadClave APICarga ShareX (endpoint legado en app.js)
POST/api/uploadClave APICarga ShareX/API (campo: file)
POST/api/web-uploadSesiónCarga web (campo: files[], máx. 500)
POST/api/chunk/initSesiónInicializar carga por partes
POST/api/chunk/:uploadIdSesiónSubir un fragmento (campo: chunk)
POST/api/chunk/:uploadId/completeSesiónEnsamblar fragmentos
DELETE/api/chunk/:uploadIdSesiónCancelar carga y limpiar

Respuesta de carga por API:

{
  "url": "https://example.com/f/a1b2c3d4",
  "raw": "https://example.com/f/a1b2c3d4/raw",
  "delete_url": "https://example.com/api/delete/a1b2c3d4",
  "short_id": "a1b2c3d4",
  "filename": "screenshot.png",
  "size": 102400
}

Flujo de carga por partes

1. POST /api/chunk/init{ uploadId: "32hex" }

2. POST /api/chunk/:uploadId (Cuerpo: chunkIndex=N, Archivo: chunk) → { received: N }

En paralelo con 3–5 fragmentos simultáneos

3. POST /api/chunk/:uploadId/complete{ files: [fileObject] }


Gestión de archivos

MétodoRutaAuthDescripción
GET/api/gallerySesiónArchivos propios (admin: todos), paginado (24/página), filtros: q, type, tag, page
GET/api/file/:shortIdMetadatos del archivo (incrementa el contador de vistas)
PATCH/api/file/:shortIdSesiónActualizar etiquetas/nombre
DELETE/api/file/:shortIdSesiónEliminar archivo
DELETE/api/delete/:shortIdClave APIEliminar archivo (autenticación por clave API)
POST/api/files/bulkSesiónAcciones en masa: delete, tag, removeTag, addToCollection, moveToCollection
GET/api/tagsSesiónTodas las sugerencias de etiquetas del usuario

Parámetros de consulta de la galería:


Configuración de usuario

MétodoRutaAuthDescripción
GET/api/my-keySesiónMostrar prefijo de clave API
POST/api/regen-keySesiónRegenerar clave API
GET/api/sharex-configSesiónDescargar ShareX .sxcu (regenera la clave)
PATCH/api/user/usernameSesiónCambiar nombre de usuario (contraseña requerida)
PATCH/api/user/passwordSesiónCambiar contraseña
PATCH/api/user/emailSesiónCambiar email (envía email de verificación)
PATCH/api/user/languageSesiónEstablecer idioma de la interfaz
PATCH/api/user/embed-modeSesiónEstablecer modo de incrustación (embed/raw)
POST/api/user/resend-verificationSesiónReenviar email de verificación
GET/api/user/exportSesiónExportación de datos (RGPD Art. 20) como JSON
DELETE/api/user/accountSesiónEliminar cuenta (RGPD Art. 17, contraseña requerida)
GET/api/user/predefined-tagsSesiónObtener etiquetas predefinidas
PATCH/api/user/predefined-tagsSesiónActualizar etiquetas predefinidas
POST/api/user/avatarSesiónSubir avatar (máx. 2 MB, JPEG/PNG/GIF/WebP)
DELETE/api/user/avatarSesiónEliminar avatar
GET/api/user/avatar/:userIdServir avatar

Enlaces de compartición

MétodoRutaAuthDescripción
GET/api/file/:shortId/share-linksSesión (Propietario/Admin)Todos los enlaces de compartición de un archivo
POST/api/file/:shortId/share-linksSesión (Propietario/Admin)Crear enlace de compartición
DELETE/api/share-links/:tokenSesión (Propietario/Creador/Admin)Eliminar enlace de compartición
GET/api/share-links/:tokenMetadatos del enlace de compartición (público)
POST/api/share-links/:token/verifyVerificar contraseña del enlace de compartición

Crear enlace de compartición:

POST /api/file/a1b2c3d4/share-links
{
  "label": "Para colegas",
  "password": "secreto",
  "expiresAt": "2025-12-31T23:59:59Z",
  "downloadLimit": 10
}

Colecciones

MétodoRutaAuthDescripción
GET/api/collectionsSesiónColecciones propias (admin: todas)
POST/api/collectionsSesiónCrear colección
GET/api/collections/:idVer colección (pública, contraseña si está definida)
PATCH/api/collections/:idSesión (Propietario/Admin)Actualizar colección
DELETE/api/collections/:idSesión (Propietario/Admin)Eliminar colección
POST/api/collections/:id/filesSesión (Propietario/Admin)Añadir archivo a la colección
DELETE/api/collections/:id/files/:fileShortIdSesión (Propietario/Admin)Eliminar archivo de la colección
POST/api/collections/:id/verifyVerificar contraseña de la colección

Endpoints de administración

MétodoRutaAuthDescripción
GET/api/admin/statsAdminEstadísticas del panel de control
GET/api/admin/usersAdminTodos los usuarios (con número de archivos)
POST/api/admin/usersAdminCrear usuario
PATCH/api/admin/users/:id/toggleAdminActivar/desactivar usuario
PATCH/api/admin/users/:id/roleAdminCambiar rol de usuario
DELETE/api/admin/users/:idAdminEliminar usuario
POST/api/admin/users/:id/regen-keyAdminRegenerar clave API
PATCH/api/admin/users/:id/passwordAdminEstablecer contraseña
PATCH/api/admin/users/:id/folderAdminCambiar nombre de carpeta (mueve archivos)
GET/api/admin/filesAdminTodos los archivos, paginado (30/página)
GET/api/admin/site-settingsAdminLeer configuración del operador
PATCH/api/admin/site-settingsAdminActualizar configuración del operador
GET/api/admin/audit-logAdminRegistro de auditoría paginado (50/página)
GET/api/admin/audit-log/exportAdminDescargar registro de auditoría como CSV
GET/api/site-settingsInformación pública del operador (para la página de privacidad)

Configuración del sitio (pública)

GET /api/site-settings
{
  "operatorName": "Musterfirma GmbH",
  "operatorAddress": "Musterstr. 1, 12345 Musterstadt",
  "operatorEmail": "[email protected]",
  "cloudflareAnalytics": false,
  "fileRetentionDays": 365,
  "encryptionAtRest": false,
  "sessionDurationDays": 7
}

8. API WebSocket

Conexión: wss://example.com/ws (solo para usuarios con sesión iniciada, se requiere cookie de sesión)

Protocolo

Cliente → Servidor (Solicitud):

{ "id": "req-abc123", "action": "file:list", "payload": { "type": "image", "page": 1 } }

Servidor → Cliente (Respuesta):

{ "id": "req-abc123", "data": { ... } }

Servidor → Cliente (Error):

{ "id": "req-abc123", "error": "Forbidden", "status": 403 }

Servidor → Cliente (Difusión):

{ "event": "file:uploaded", "data": { "shortId": "a1b2c3d4", "uploaderId": "..." } }

Acciones disponibles

AcciónAuthDescripción
site-settings:getConfiguración pública del sitio
auth:meUsuarioDatos del usuario conectado
file:getUsuarioDetalles del archivo (incrementa vistas)
file:listUsuarioLista de archivos con filtro/paginación
file:deleteUsuarioEliminar archivo
user:get-keyUsuarioPrefijo de clave API
user:regen-keyUsuarioRegenerar clave API
user:change-passwordUsuarioCambiar contraseña
user:change-usernameUsuarioCambiar nombre de usuario
user:change-emailUsuarioCambiar email
user:change-languageUsuarioEstablecer idioma
user:change-embed-modeUsuarioEstablecer modo de incrustación
user:resend-verificationUsuarioReenviar email de verificación
user:exportUsuarioExportación de datos
user:delete-accountUsuarioEliminar cuenta
admin:statsAdminEstadísticas del panel
admin:settings:getAdminLeer configuración del sitio
admin:settings:updateAdminActualizar configuración del sitio
admin:users:listAdminTodos los usuarios
admin:users:createAdminCrear usuario
admin:users:toggleAdminActivar/desactivar usuario
admin:users:roleAdminCambiar rol de usuario
admin:users:deleteAdminEliminar usuario
admin:users:regen-keyAdminRegenerar clave API
admin:users:passwordAdminEstablecer contraseña
admin:users:folderAdminCambiar nombre de carpeta
admin:files:listAdminTodos los archivos
admin:audit-log:listAdminRegistro de auditoría paginado

Eventos de difusión

EventoDestinatariosCarga útil
file:uploadedCargador{ shortId, uploaderId }
file:deletedPropietario del archivo{ shortId, uploaderId }
file:viewTodos{ shortId, views }
user:createdAdmins{ id, username, role, ... }
user:deletedAdmins{ id }
user:updatedAdmins{ id, ...campos modificados }
audit:logAdminsObjeto AuditLog completo
settings:updatedAdminsObjeto SiteSettings actualizado
stats:invalidateAdmins{} (activa la actualización de estadísticas)

9. Rutas de servicio de archivos

/f/:shortId — Visor de archivos

- embedMode = 'embed': HTML OG con redirección

- embedMode = 'raw' + imagen/vídeo/audio: HTTP 302 → /f/:shortId/raw

/f/:shortId/raw — Acceso directo

Sirve el archivo como respuesta HTTP con soporte de solicitudes de rango (206 Partial Content). Imágenes/vídeos/audio: Content-Disposition: inline, otros: attachment.

/f/:shortId/download — Descarga forzada

Como /raw, pero siempre Content-Disposition: attachment.

/f/:shortId/thumb — Miniatura

Devuelve la miniatura JPEG (para vídeos y PDFs). Cache-Control: public, max-age=86400.

/f/:shortId/delete/:token — Eliminación ShareX

Elimina el archivo sin sesión mediante un token de eliminación único.

/s/:token — Servicio de archivos por enlace de compartición (src/routes/shares.js)

Verifica la contraseña (mediante flag de sesión), la fecha de expiración y el límite de descargas, y luego sirve el archivo.


10. Autenticación y seguridad

Autenticación por sesión

Autenticación por clave API

Cadena de middleware (src/middleware/auth.js)

requireLogin    → verifica req.session.user → 401 si no está conectado
requireAdmin    → como requireLogin, además role === 'admin' → 403
requireApiKey   → verifica la cabecera Authorization o req.body.token

Protección CSRF

requireSameOrigin() en app.js compara la cabecera Origin con la cabecera Host para todas las rutas API. Complementa las cookies sameSite: 'strict'.

Política de seguridad de contenido

default-src 'self';
script-src 'self' 'unsafe-inline' https://static.cloudflareinsights.com;
style-src 'self' 'unsafe-inline';
img-src 'self' data: blob:;
media-src 'self' blob:;
connect-src 'self' https://cloudflareinsights.com;
frame-ancestors 'self';

Cabeceras de seguridad

Limitación de tasa

EndpointLímiteVentana
Carga (/upload, /api/upload, /api/web-upload)60 solicitudes15 minutos
Auth (/api/auth/login, /register, etc.)10 solicitudes15 minutos
Restablecimiento de contraseña5 solicitudes1 hora

Lista de bloqueo de archivos

Los siguientes tipos MIME y extensiones son rechazados en la carga:

Tipos MIME bloqueados: application/x-executable, application/x-sh, application/x-csh, application/x-bat

Extensiones bloqueadas: .bat, .cmd, .com, .ps1, .psm1, .psd1, .sh, .bash, .csh, .zsh, .fish, .vbs, .vbe, .jse, .scr, .pif, .application, .gadget, .hta, .php, .php3–5, .phtml, .asp, .aspx, .jsp, .jspx, .cfm

Protección contra path traversal

Todos los accesos a archivos mediante resolveUploadPath() verifican que la ruta resuelta se encuentre dentro de UPLOAD_DIR.


11. Sistema de carga

Carga estándar (Multer)

Carga por partes (>250 MB)

El cliente frontend cambia automáticamente al modo de fragmentos para archivos grandes.

Estructura de directorios en el servidor:

uploads/.chunks/{uploadId}/
  meta.json          { filename, mimeType, totalSize, totalChunks, userId, createdAt }
  chunk-0
  chunk-1
  ...
  chunk-N

Tamaño de fragmento: 10–20 MB (máx. 51 MB aceptado para compatibilidad con versiones anteriores)

Paralelismo: 3–5 cargas de fragmentos simultáneas

Ensamblado: Basado en streams (sin carga completa en RAM)

Carga de avatar


12. Generación de miniaturas

Las miniaturas se generan de forma asíncrona después de la carga (.catch(() => {}) — los errores se ignoran silenciosamente).

Miniaturas de vídeo (ffmpeg)

ffmpeg -y -i <file> -ss 00:00:01 -vframes 1 \
  -vf "scale=320:320:force_original_aspect_ratio=increase,crop=320:320" \
  -q:v 3 uploads/.thumbnails/<shortId>.jpg

Miniaturas de PDF (Ghostscript)

gs -dNOPAUSE -dBATCH -dSAFER \
  -sDEVICE=jpeg -dFirstPage=1 -dLastPage=1 \
  -r72 -dJPEGQ=85 \
  -sOutputFile=uploads/.thumbnails/<shortId>.jpg \
  <file>

Tiempo de espera: 30 segundos por generación de miniatura

Alternativa: Si ffmpeg/ghostscript no está disponible, la generación se omite silenciosamente.

Script de relleno

npm run migrate:thumbnails
# o en el contenedor:
docker exec -it <container> npm run migrate:thumbnails

13. Correo electrónico y SMTP

Configuración

SMTP se activa cuando se establece SMTP_HOST. mailer.isConfigured() verifica este valor.

Plantillas de correo electrónico

Todos los correos se envían en el idioma del usuario (8 idiomas). Las plantillas están integradas en src/utils/mailer.js.

Tipos de correos enviados:

TipoDisparadorValidez del token
Verificación de emailRegistro, cambio de email24 horas
Restablecimiento de contraseñaPOST /api/auth/forgot-password1 hora

Seguridad de los tokens


14. Internacionalización

Biblioteca: i18next + react-i18next + i18next-browser-languagedetector

Idiomas admitidos:

CódigoIdioma
enEnglish
deDeutsch
frFrançais
esEspañol
itItaliano
ptPortuguês
ja日本語
zh中文

Selección de idioma:

1. Detección del idioma del navegador (automática)

2. Preferencia del usuario en la base de datos (user.language)

3. Persistencia mediante PATCH /api/user/language

Archivos de traducción: client/src/i18n/locales/{code}.json


15. Frontend (React SPA)

Configuración del enrutador (client/src/App.jsx)

RutaComponenteAuth
/Redirección → /galleryNo
/auth/loginLogin.jsxNo
/auth/registerRegister.jsxNo
/auth/forgot-passwordForgotPassword.jsxNo
/auth/reset-passwordResetPassword.jsxNo
/installInstall.jsxNo
/uploadUpload.jsx
/galleryGallery.jsx
/f/:shortIdFileView.jsx
/collectionsCollections.jsx
/c/:idCollectionView.jsxNo (público)
/s/:tokenShareView.jsxNo (público)
/settingsSettings.jsx
/adminDashboard.jsxAdmin
/admin/usersUsers.jsxAdmin
/admin/filesFiles.jsxAdmin
/admin/audit-logAuditLog.jsxAdmin
/admin/site-settingsSiteSettings.jsxAdmin
/admin/importImport.jsxAdmin
/privacyPrivacyPolicy.jsxNo
/termsTermsOfService.jsxNo

Contexto de autenticación (client/src/context/AuthContext.jsx)

Estado global para el usuario con sesión iniciada. Inicializado al inicio de la aplicación mediante GET /api/auth/me.

Hook WebSocket (client/src/hooks/useWebSocket.js)

Gestiona la conexión WS persistente. Proporciona sendMessage() y registro de manejadores de eventos. Lógica de reconexión en caso de desconexión.

Componentes UI

Basado en shadcn/ui (Radix UI Primitives + Tailwind CSS):


16. Panel de administración

El panel de administración (/admin/*) es accesible únicamente para usuarios con role: 'admin'.

Panel de control (/admin)

Gestión de usuarios (/admin/users)

Gestión de archivos (/admin/files)

Registro de auditoría (/admin/audit-log)

Configuración del sitio (/admin/site-settings)

Importación XBackBone (/admin/import)


17. RGPD / Privacidad

FuncionalidadArtículo RGPD
Política de privacidad (configurable)Art. 13/14 – Transparencia
Página de términos de servicio (configurable)Art. 13/14 – Transparencia
Exportación de datos (JSON con URLs)Art. 20 – Portabilidad de datos
Autoelimación de cuenta (archivos + datos)Art. 17 – Derecho de supresión
Registro de auditoría (TTL 90 días vía MongoDB)Art. 5(2) – Responsabilidad
Exportación CSV del registro de auditoríaArt. 5(2) – Responsabilidad
Retención de archivos configurableArt. 5(1)(e) – Limitación del plazo de conservación
Claves API como hash SHA-256Art. 32 – Seguridad
Contraseñas como bcrypt (12 rondas)Art. 32 – Seguridad
Consentimiento de cookies para Cloudflare AnalyticsArt. 13 – Transparencia
Anonimización al eliminar la cuentaArt. 17 – Derecho de supresión

Flujo de eliminación RGPD

Al eliminar la cuenta (user:delete-account / DELETE /api/user/account):

1. Todos los archivos del usuario se eliminan del disco

2. Las miniaturas se eliminan

3. El avatar se elimina

4. Las entradas del registro de auditoría se anonimizan (username: '[deleted]', ip: null, userId: null)

5. El documento de usuario se elimina

6. La sesión se destruye


18. Tareas en segundo plano

Limpieza por retención (src/jobs/retentionCleanup.js)

Índices TTL de MongoDB (automáticos)

ColecciónTTLDisparador
AuditLog90 díastimestamp
Collection7 días después de expiresAtexpiresAt
ShareLink7 días después de expiresAtexpiresAt

19. Despliegue

Docker (recomendado)

git clone https://github.com/Christianoooooo/sharely.git
cd sharely
cp .env.example .env
# Editar .env (SESSION_SECRET, contraseñas MONGO, BASE_URL)
docker compose up -d

Servicios:

Volúmenes:

Comprobaciones de salud: La aplicación verifica HTTP 200 en /, MongoDB verifica db.adminCommand('ping').

Proxy inverso Nginx

server {
    listen 443 ssl;
    server_name files.example.com;

    client_max_body_size 2100M;  # Al menos tan grande como el fragmento más grande + buffer

    location / {
        proxy_pass http://localhost:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # Soporte WebSocket
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}
BASE_URL en .env debe coincidir con el dominio público.

Primer inicio

El primer usuario registrado recibe automáticamente el rol admin (User.countDocuments() === 0).


20. Entorno de desarrollo

Requisitos previos

Configuración

# Backend
npm install
cp .env.example .env
# Editar .env

# Frontend
cd client
npm install

Inicio

# Backend (puerto 3000, con nodemon)
npm run dev

# Frontend (puerto 5173, terminal separada)
cd client
npm run dev

El servidor de desarrollo Vite redirecciona automáticamente las solicitudes API a localhost:3000.

Compilación

npm run build   # Compila client/dist/, el backend permanece sin cambios
npm start       # Inicia el servidor Express de producción

21. Migraciones y scripts

Migraciones automáticas (en cada inicio)

Estas migraciones se ejecutan al iniciar la aplicación en app.js y son idempotentes:

MigraciónArchivoFunción
Migración de carpetas de usuariosrc/migrations/migrateUserFolders.jsMueve archivos de uploads/ a uploads/{folderName}/
Migración de hashes de claves APIsrc/migrations/migrateApiKeyHashes.jsConvierte claves API en texto en claro a hashes SHA-256

Scripts manuales

# Generar miniaturas para archivos existentes
npm run migrate:thumbnails
# o:
node scripts/generate-missing-thumbnails.js

# Mover cargas a carpetas de usuario (manual)
npm run migrate:user-folders
# o:
node scripts/migrate-uploads-to-user-folders.js

Inicialización de la base de datos (Docker)

scripts/mongo-init.js se ejecuta en el primer inicio del contenedor MongoDB y crea el usuario de aplicación con los permisos correctos.


22. Pruebas end-to-end

Framework: Playwright (@playwright/test)

Archivos de prueba

ArchivoSuite de pruebas
e2e/upload.spec.jsFlujos de carga
e2e/gallery.spec.jsGalería y gestión de archivos
e2e/admin.spec.jsPanel de administración
e2e/sharelink.spec.jsCreación y uso de enlaces de compartición
e2e/tags.spec.jsGestión de etiquetas
e2e/bulk-actions-fixes.spec.jsAcciones en masa

Ejecución

# Todas las pruebas
npm run test:e2e

# Con interfaz gráfica
npm run test:e2e:ui

Configuración Playwright: playwright.config.js

Configuración global: e2e/global-setup.js (crea usuarios de prueba, admin, etc.)

Utilidades: e2e/helpers.js (funciones auxiliares compartidas)


Apéndice: Acciones del registro de auditoría

AcciónDisparador
loginInicio de sesión exitoso
logoutCierre de sesión
registerRegistro
uploadCarga de archivo
delete_fileArchivo eliminado
delete_accountCuenta eliminada
change_passwordContraseña cambiada
change_usernameNombre de usuario cambiado
change_emailEmail cambiado
verify_emailEmail verificado
forgot_passwordRestablecimiento de contraseña solicitado
reset_passwordContraseña restablecida
regen_api_keyClave API regenerada
sharex_configConfiguración ShareX descargada
export_dataExportación de datos
admin_create_userAdmin: usuario creado
admin_delete_userAdmin: usuario eliminado
admin_toggle_userAdmin: usuario activado/desactivado
admin_change_roleAdmin: rol de usuario cambiado
admin_change_passwordAdmin: contraseña establecida
admin_regen_keyAdmin: clave API regenerada

*Documentación generada a partir del código fuente de sharely v1.0.0*