API Express + TypeScript para aplicar cambios de esquema en MongoDB de forma segura, auditable y trazable, con integraciones opcionales hacia Artifactory y Jira.
- Resumen rápido
- Requisitos previos
- Primeros pasos
- Variables de entorno
- Scripts disponibles
- Consumir la API
- Integrando Artifactory
- Integrando Jira
- Ejemplo completo de payload
- Trazabilidad y monitoreo
- Solución de problemas
- Autenticación JWT HS256 y verificación HMAC opcional en operaciones de escritura.
- Middleware de seguridad: Helmet, CORS configurable, allowlist de IP y rate limiting.
- Ventana de cambios configurable con posibilidad de bypass controlado.
- Operaciones idempotentes
createCollection
ycreateIndex
, con auditoría, revert y rollback en lotes. - Documentación OpenAPI disponible en
/docs
(Swagger UI) y/redoc
. - Integraciones opcionales:
- Artifactory: genera un artefacto JSON por operación.
- Jira: crea o actualiza issues con comentarios operativos.
- Node.js 18 o superior.
- Acceso a una instancia de MongoDB.
- (Opcional) Credenciales válidas para Artifactory y/o Jira si las integraciones se habilitan.
- Clona el repositorio y entra en la carpeta del proyecto.
- Copia
.env.example
a.env
y completa los valores necesarios. - Instala las dependencias:
npm install
- Ejecuta el proyecto en modo desarrollo:
npm run dev
- Abre
http://localhost:8080/docs
para navegar la especificación interactiva.
Variable | Descripción |
---|---|
PORT |
Puerto HTTP de la API (por defecto 8080 ). |
AUDIT_DB |
Base de datos donde se guardan los registros de auditoría. |
JWT_SECRET |
Secreto HS256 para validar JWT (obligatorio si JWT_REQUIRED=true ). |
HMAC_SECRET |
Clave para validar la firma HMAC de los cuerpos en escrituras. |
RATE_LIMIT_WINDOW_MS , RATE_LIMIT_MAX |
Configuración del limitador global. |
IP_ALLOWLIST |
Lista (coma) de IP/CIDR permitidos. Vacío = sin restricción. |
CORS_ORIGINS |
Orígenes permitidos para CORS. Vacío = * . |
OPLOG_ENABLE , OPLOG_DIR |
Control de logs operativos (NDJSON diario). |
CHANGE_ALLOW_WINDOW |
Cadena que define ventana de cambios permitidos. |
CHANGE_FREEZE_MESSAGE |
Mensaje devuelto cuando la ventana bloquea la acción. |
CHANGE_BYPASS_TOKEN |
Token opcional para saltar la ventana de cambios. |
ALLOW_TARGET_URI_REGEX |
Regex opcional para validar URIs de destino. |
Variable | Descripción |
---|---|
ARTIFACTORY_ENABLED |
Activa la publicación (true /false ). |
ARTIFACTORY_BASE_URL |
URL base (ej. https://artifactory.miempresa.com/artifactory ). |
ARTIFACTORY_REPOSITORY |
Repositorio por defecto para los artefactos. |
ARTIFACTORY_PATH_TEMPLATE |
Plantilla de ruta (changes/{changeId}/{action}-{timestamp}.json ). |
ARTIFACTORY_USERNAME , ARTIFACTORY_PASSWORD |
Credenciales básicas (alternativa al token). |
ARTIFACTORY_TOKEN |
Token API (X-JFrog-Art-Api ). |
ARTIFACTORY_TIMEOUT_MS |
Timeout de la solicitud HTTP. |
Variable | Descripción |
---|---|
JIRA_ENABLED |
Activa la sincronización (true /false ). |
JIRA_BASE_URL |
URL base de Jira (ej. https://miempresa.atlassian.net ). |
JIRA_EMAIL |
Cuenta usada para autenticarse. |
JIRA_API_TOKEN |
Token API asociado a JIRA_EMAIL . |
JIRA_PROJECT_KEY |
Proyecto por defecto al crear issues. |
JIRA_ISSUE_TYPE |
Tipo de issue (por defecto Task ). |
JIRA_DEFAULT_LABELS |
Etiquetas por defecto separadas por comas. |
JIRA_TIMEOUT_MS |
Timeout de la solicitud HTTP. |
Consejo: reutiliza el bloque completo de
.env.example
para evitar errores tipográficos.
Comando | Descripción |
---|---|
npm run dev |
Inicia el servidor con recarga en caliente (ts-node-dev ). |
npm run build |
Compila TypeScript a JavaScript en dist/ . |
npm start |
Ejecuta la versión compilada desde dist/ . |
- Genera un JWT firmado con
JWT_SECRET
. Puedes usarjsonwebtoken
:npm i jsonwebtoken node -e "const jwt=require('jsonwebtoken');console.log(jwt.sign({sub:'user-1',email:'dev@local'}, process.env.JWT_SECRET,{algorithm:'HS256',expiresIn:'1h'}));"
- Para aplicar un cambio individual:
curl -X POST "http://localhost:8080/changes/apply" \ -H "Authorization: Bearer <TOKEN>" \ -H "Content-Type: application/json" \ -d '{ "target": { "uri": "mongodb://user:pass@127.0.0.1:27017/admin", "database": "MyDB" }, "operation": { "type": "createIndex", "collection": "users", "spec": { "email": 1 }, "options": { "name": "ix_email_unique", "unique": true } } }'
- Usa
/changes?uri=<URI codificada>
para consultar auditoría. Añadestatus=applied,failed,skipped,reverted
si necesitas incluir revertidos.
Cuando dryRun=true
, la API valida y genera plan, pero no toca MongoDB ni dispara integraciones.
- Establece
ARTIFACTORY_ENABLED=true
en.env
. - Define
ARTIFACTORY_BASE_URL
yARTIFACTORY_REPOSITORY
. - Configura autenticación: usuario/contraseña o token API.
- Reinicia el servicio para aplicar los cambios.
Para cada operación se genera un JSON con:
- Información del cambio original (incluyendo metadatos).
- Resultado (
status
,message
,durationMs
,revertPlan
). - Contexto operativo (
action
,timestamp
,requestId
, información de lote, actor`).
La respuesta HTTP incluye integrations.artifactory
con el resultado de la publicación (enabled
, success
, details
, error
, skippedReason
).
Dentro del payload, usa metadata.artifactory
:
"metadata": {
"artifactory": {
"repository": "db-changes",
"path": "changes/{changeId}/{action}-{timestamp}.json",
"properties": {
"environment": "prod",
"service": "payments-api"
},
"skip": false
}
}
- La plantilla acepta
{changeId}
,{action}
,{timestamp}
,{collection}
y{operation}
. properties
genera parámetros de matriz (;clave=valor
).- Usa
skip: true
si quieres omitir la publicación en un caso concreto.
- Establece
JIRA_ENABLED=true
. - Define
JIRA_BASE_URL
,JIRA_PROJECT_KEY
,JIRA_EMAIL
yJIRA_API_TOKEN
. - Ajusta
JIRA_ISSUE_TYPE
yJIRA_DEFAULT_LABELS
si lo necesitas. - Reinicia el servicio.
Comportamiento por defecto:
- Si no se envía
metadata.jira.issueKey
, la API crea un nuevo issue usando los valores derivados del cambio. - Cada ejecución agrega un comentario con
action
,status
,message
ytimestamp
. - La respuesta incluye
integrations.jira
conissueKey
,created
,commentId
,url
, o en su defecto,error
/skippedReason
.
"metadata": {
"jira": {
"issueKey": "OPS-1234",
"summary": "Crear índice único para pagos",
"description": "Detalles extendidos...",
"labels": ["db-change", "payments"],
"components": ["Payments"],
"assignee": "db.automation",
"skip": false
}
}
issueKey
reutiliza un ticket existente.summary
ydescription
sobreescriben los generados.labels
,components
yassignee
complementan o sustituyen la configuración global.skip: true
evita que esa petición dispare Jira.linkIssues
está reservado para futuras funciones.
{
"target": {
"uri": "mongodb://user:pass@cluster/?replicaSet=rs0&authSource=admin",
"database": "Billing"
},
"operation": {
"type": "createIndex",
"collection": "payments",
"spec": { "invoiceId": 1 },
"options": { "name": "idx_invoice_unique", "unique": true }
},
"metadata": {
"artifactory": {
"repository": "db-changes",
"path": "changes/{changeId}/{action}-{timestamp}.json",
"properties": {
"environment": "prod",
"service": "payments-api"
}
},
"jira": {
"summary": "Crear índice único para pagos",
"labels": ["db-change", "payments"],
"components": ["Payments"],
"assignee": "db.automation"
}
}
}
- Logs operativos: si
OPLOG_ENABLE=true
, se generan archivos NDJSON diarios enlogs/ops-YYYYMMDD.log
. - Auditoría en MongoDB: colección
cicd_changes_audit
con detalle de cada cambio y surevertPlan
. - Respuesta del API: el bloque
integrations
confirma qué ocurrió en Artifactory y Jira.
Problema | Acción sugerida |
---|---|
401 Unauthorized |
Verifica el JWT (firmado con el secreto correcto y sin expirar). |
403 en /changes/* |
IP fuera del allowlist, ventana de cambios cerrada o firma HMAC inválida. |
integrations.artifactory.skippedReason = "missing_credentials" |
Falta token/API key o usuario/contraseña en .env . |
Errores _failed_4xx/5xx en Jira |
Revisa permisos, datos obligatorios y proyecto configurado. |
Integraciones omitidas en dryRun |
Comportamiento esperado: no se contacta Artifactory/Jira durante simulaciones. |
- Recursos necesarios
- Cuenta con privilegios administrativos en Artifactory (Cloud o self-hosted).
- Cuenta Atlassian con permiso para crear proyectos en Jira Software.
- Acceso a una instancia MongoDB y al servidor donde se ejecutará esta API.
- Postman instalado (o Postman Web + agente local).
- Repositorio local: clona este proyecto y asegúrate de instalar dependencias (
npm install
).
- Crear instancia
- Para pruebas rápidas puedes usar JFrog Cloud Free/Trial o levantar JFrog Platform en Docker.
- Crear repositorio genérico
- Ingresa a Administration → Repositories → Create Repository → Generic (Local).
- Asigna un nombre (ej.
db-changes
) y guarda la configuración por defecto.
- Crear usuario técnico o token
- Opciones:
- Usuario dedicado con permiso
deploy
sobre el repo recién creado. - Token API (administración →
User Management
→Access Tokens
).
- Usuario dedicado con permiso
- Anota
username/password
o el token, la base URL de Artifactory (https://<host>/artifactory
) y el nombre del repositorio.
- Opciones:
- Crear proyecto
- Entra a admin.atlassian.com → Jira Software → Projects → Create project → Plantilla Kanban/Scrum clásica.
- Define la clave del proyecto (ej.
OPS
).
- Generar API Token
- Visita https://id.atlassian.com/manage-profile/security/api-tokens y genera un nuevo token.
- Guarda correo y token; se usarán para autenticación básica (email + token).
- Configurar
.env
ARTIFACTORY_ENABLED=true ARTIFACTORY_BASE_URL=https://<tu-host>/artifactory ARTIFACTORY_REPOSITORY=db-changes ARTIFACTORY_USERNAME=<usuario> # O usa ARTIFACTORY_TOKEN ARTIFACTORY_PASSWORD=<contraseña> ARTIFACTORY_TIMEOUT_MS=20000 JIRA_ENABLED=true JIRA_BASE_URL=https://<tu-org>.atlassian.net JIRA_EMAIL=<correo@tuorg.com> JIRA_API_TOKEN=<token_api> JIRA_PROJECT_KEY=OPS JIRA_ISSUE_TYPE=Task JIRA_DEFAULT_LABELS=db-change,automation JIRA_TIMEOUT_MS=20000
- Levantar la API
npm run dev
- Verifica en consola que no existan errores de conexión.
- Preparar collection en Postman
- Crea un Environment con variables:
baseUrl
:http://localhost:8080
jwt
: token válido (puedes generarlo con el snippet de este README).mongoUri
: URI del cluster de pruebas.database
: nombre de la base (ej.Billing
).artifactoryRepo
:db-changes
(opcional).jiraIssueKey
: deja vacío; se completará automáticamente.
- Crea un Environment con variables:
- Request: POST
${{baseUrl}}/changes/apply
- Headers:
Authorization: Bearer {{jwt}}
Content-Type: application/json
- (Opcional)
X-Request-Id: {{uuid}}
para trazabilidad.
- Body (raw JSON):
{ "target": { "uri": "{{mongoUri}}", "database": "{{database}}" }, "operation": { "type": "createIndex", "collection": "payments", "spec": { "invoiceId": 1 }, "options": { "name": "idx_invoice_unique", "unique": true } }, "metadata": { "artifactory": { "repository": "{{artifactoryRepo}}", "path": "changes/{changeId}/{action}-{timestamp}.json", "properties": { "environment": "dev", "service": "payments-api" } }, "jira": { "summary": "Crear índice unique para invoices", "labels": ["db-change", "payments"], "components": ["Payments"] } } }
- Headers:
- Enviar petición y revisar respuesta:
status
debe ser200
.integrations.artifactory.success
→true
ydetails.url
apuntando al JSON en Artifactory.integrations.jira.details.issueKey
→ clave del issue creado.- Guarda
changeId
como variable Postman para pruebas posteriores (ej. revert).
- Validaciones manuales
- Artifactory: descarga el artefacto y confirma que incluye
change
,result
ycontext
. - Jira: abre el issue (
details.url
) y verifica el comentario conAction
,Status
,Message
yTimestamp
.
- Artifactory: descarga el artefacto y confirma que incluye
- Prueba de re-ejecución
- Reenvía la misma petición; Artifactory debe registrar un segundo artefacto y Jira debe añadir un nuevo comentario (la integración no crea issue nuevo).
- Rollback opcional
- POST
${{baseUrl}}/changes/revert
con body{ "changeId": "<id>", "uri": "{{mongoUri}}" }
. - Verifica que Artifactory reciba el artefacto de tipo
revert
y que Jira sume otro comentario conaction: revert
.
- POST
Buenas prácticas: usa entornos de ensayo, activa
dryRun=true
antes de ejecutar contra producción, documenta losissueKey
resultantes y automatiza limpieza (drop index/colección) al finalizar las pruebas.
¿Planeas integrarlo en un pipeline? Orquesta los JSON desde tu CI/CD y usa el resumen de integraciones para alimentar tableros operativos, reportes o alertas automáticas.