17 Commits

Author SHA1 Message Date
msaldain 5342fb489d Actualización de archivos compose para centralizar y tanto NPM como El gestó de db's dentro de un proyecto propio "suitecoffee_tools" con acceso a las redes de producción como de desarrollo 2025-08-21 17:34:53 +00:00
msaldain c42814f963 Varios cambios realizados en cuando a la organización de los compose de docker.
Se adoptó la versión actualmente recomandada por el equipo de docker el formado "compose.yaml"

También se crearon nuevos scripts y actualizaron algunos para adaptarse a los nuevos archivos.
2025-08-19 18:26:21 +00:00
msaldain 0d1de7f7e2 Varios cambios realizados en cuando a la organización de los compose de docker.
Se adoptó la versión actualmente recomandada por el equipo de docker el formado "compose.yaml"

También se crearon nuevos scripts y actualizaron algunos para adaptarse a los nuevos archivos.
2025-08-19 18:26:13 +00:00
msaldain b34433a71e mecanismo para respaldar los volumenes del proyecto 2025-08-18 21:31:28 +00:00
msaldain 492d844523 Todos los problemas de dependencias, credenciales y renderizado de raiz se solucionaron hasta quí 2025-08-18 20:35:47 +00:00
msaldain 8237e38164 Más dependencias solucionadas. Errores en los documentos docker-compose 2025-08-18 19:52:46 +00:00
msaldain e04be61952 Modificado el docker-compose L67.
Servicio auth. Estaba importandoce las dependencias de app no de auth...
2025-08-18 19:32:32 +00:00
msaldain 1b7e4f36e9 Más actualización de dependencias 2025-08-18 19:14:00 +00:00
msaldain d8cc6e9613 Puesta a punto de dependencias.
Falla renderización en la raíz del sistema
2025-08-18 19:13:56 +00:00
msaldain aa04270550 Actualización de uso de entornos 2025-08-18 14:39:19 +00:00
msaldain 2b47faf66a Script suiecoffee.py detecta contenedores de un entorno u otro ene ejeccuón, también los inicia o deteniene desde un menú.
Se agregó dentro del  docker-compose.yml la variable

name: ${COMPOSE_PROJECT_NAME:-suitecoffee}

Para crear el nombre del proyecto, el nombre está defininido en los .env respectivos para cada entorno
2025-08-17 04:37:18 +00:00
msaldain 97db600b1f Actualizaciones de archivo.
Modificaciones pequeñas
2025-08-16 03:25:24 +00:00
msaldain f9bf5f4824 Intento de utilizar nodemon development para desarrollar son estropear production. 2025-08-16 02:18:50 +00:00
msaldain 1db2f11c19 Se crearon servicios de gestores de bases de datos que permitan gestionar las bases de una forma más práctica.
- dbeaver
- adminer (respaldo)
2025-08-15 21:29:53 +00:00
msaldain 511b370a2e Se configuró un Nginx Proxy Manager que permita adminitrad más códodamente los endpoints tanto de apis como de archivos .html servidor tanto por auth/ y app/ 2025-08-15 17:02:48 +00:00
msaldain 3d3ef3f002 Ahora Suitecoffee puede correr dentro de contenedores docker. Mediante docker compose sin necesidad de exponer puertos, únicamente se expone un puerto 80 del servicio 'gateway' que utiliza una imagen nginx:alpine.
el comando para levantar el servicio con el entorno de desarrollo (utilizando las variables desarrollo + docker.compose.override.yml) es:

docker compose -f docker-compose.yml -f docker-compose.override.yml --env-file .env.development up -d

Para desactivarlo:

docker compose -f docker-compose.yml -f docker-compose.override.yml --env-file .env.development down
2025-08-15 14:30:35 +00:00
msaldain abaf43b8d6 auth - registro... planes
Se pudo crear la tabla 'plan' donde se encuentran los plane que el usuario puede selecionar al registrarse en el sistema. verificado que el código funciona por que tráe los diferentes planes.

Aún no se pudo registrar un nuevo usuario. Se intentará resolver
2025-08-15 03:35:26 +00:00
25 changed files with 2788 additions and 552 deletions
+3
View File
@@ -1,6 +1,9 @@
# Ignorar los directorios de dependencias # Ignorar los directorios de dependencias
node_modules/ node_modules/
# Ignorar los volumenes respaldados
docker-volumes*
# Ignorar las carpetas de bases de datos # Ignorar las carpetas de bases de datos
.db/ .db/
-116
View File
@@ -1,116 +0,0 @@
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>Cargar Comanda</title>
</head>
<body>
<h2>Formulario de Carga de Comanda</h2>
<form id="formComanda">
<label for="mesaSelect">Mesa:</label>
<select id="mesaSelect" name="mesa_id" required></select>
<br><br>
<label for="productoSelect">Producto:</label>
<select id="productosSelect"></select>
<label for="cantidadInput">Cantidad:</label>
<input type="number" id="cantidadInput" min="1" value="1">
<button type="button" onclick="agregarProducto()">Agregar</button>
<ul id="listaProductos"></ul>
<input type="hidden" name="productos" id="productosJSON">
<br>
<button type="submit">Guardar Comanda</button>
</form>
<script>
// Cargar categorías y mesas (productos se maneja distinto por el precio)
async function cargarSelect(endpoint, selectId, mostrar) {
const res = await fetch(endpoint);
const data = await res.json();
const select = document.getElementById(selectId);
data.forEach(item => {
const option = document.createElement('option');
option.value = item.id;
option.textContent = mostrar(item);
select.appendChild(option);
});
}
async function cargarProductosConPrecio() {
const res = await fetch('/api/obtenerProductos');
const productos = await res.json();
const select = document.getElementById('productosSelect');
productos.forEach(prod => {
const option = document.createElement('option');
option.value = prod.id;
option.textContent = `${prod.nombre} ($${prod.precio})`;
option.setAttribute('data-precio', prod.precio);
select.appendChild(option);
});
}
const listaProductos = [];
function agregarProducto() {
const select = document.getElementById('productosSelect');
const cantidad = parseInt(document.getElementById('cantidadInput').value);
const productoId = select.value;
const nombre = select.options[select.selectedIndex].textContent;
const precioUnitario = parseFloat(select.options[select.selectedIndex].dataset.precio);
if (!productoId || isNaN(cantidad) || cantidad <= 0) {
alert("Producto o cantidad inválida");
return;
}
listaProductos.push({
producto_id: productoId,
cantidad,
precio_unitario: precioUnitario
});
// Mostrar en lista visual
const li = document.createElement('li');
li.textContent = `${nombre} x${cantidad} - $${(precioUnitario * cantidad).toFixed(2)}`;
document.getElementById('listaProductos').appendChild(li);
// Actualizar JSON oculto
document.getElementById('productosJSON').value = JSON.stringify(listaProductos);
}
document.getElementById('formComanda').addEventListener('submit', async function (e) {
e.preventDefault();
const mesaId = document.getElementById('mesaSelect').value;
const productos = listaProductos;
if (!mesaId || productos.length === 0) {
alert('Selecciona una mesa y al menos un producto');
return;
}
const res = await fetch('/api/cargarComandas', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
mesa_id: mesaId,
productos: productos
})
});
const resultado = await res.json();
alert(resultado.mensaje || 'Comanda cargada correctamente');
location.reload();
});
window.onload = () => {
cargarSelect('/api/obtenerMesas', 'mesaSelect', mesa => `Mesa ${mesa.numero}`);
cargarProductosConPrecio();
};
</script>
</body>
</html>
-195
View File
@@ -1,195 +0,0 @@
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="utf-8" />
<title>Crear comanda</title>
</head>
<body>
<h1>Crear comanda</h1>
<section>
<label>
Mesa (ID o número):
<input type="number" id="mesaId" required />
</label>
<br />
<label>
Mozo (ID de usuario):
<input type="text" id="mozoId" required />
</label>
<br />
<label>
Notas:
<input type="text" id="notas" placeholder="Sin observaciones" />
</label>
</section>
<hr />
<section>
<h2>Agregar productos</h2>
<label>
Producto:
<select id="productoSelect"></select>
</label>
<label>
Cantidad:
<input type="number" id="cantidadInput" value="1" min="1" />
</label>
<button id="agregarBtn">Agregar</button>
<h3>Items de la comanda</h3>
<ul id="itemsList"></ul>
</section>
<hr />
<button id="enviarBtn">Enviar comanda</button>
<pre id="salida"></pre>
<script>
// === CONFIGURA AQUÍ SI ES NECESARIO ===
const API_BASE = "http://localhost:3000"; // Cambia al puerto/host de tu servidor Node
const PRODUCTOS_PATH = "/productos"; // GET
const COMANDAS_PATH = "/comandas"; // POST
// === ESTADO EN MEMORIA ===
const productosCache = new Map(); // id -> {id, nombre, ...}
const items = []; // {producto_id, cantidad}
// === ELEMENTOS DEL DOM ===
const productoSelect = document.getElementById("productoSelect");
const cantidadInput = document.getElementById("cantidadInput");
const agregarBtn = document.getElementById("agregarBtn");
const itemsList = document.getElementById("itemsList");
const enviarBtn = document.getElementById("enviarBtn");
const mesaIdInput = document.getElementById("mesaId");
const mozoIdInput = document.getElementById("mozoId");
const notasInput = document.getElementById("notas");
const salida = document.getElementById("salida");
// === UTILIDADES ===
function renderItems() {
itemsList.innerHTML = "";
items.forEach((it, idx) => {
const li = document.createElement("li");
const prod = productosCache.get(it.producto_id);
const nombre = prod ? (prod.nombre || prod.name || `Producto ${it.producto_id}`) : `ID ${it.producto_id}`;
li.textContent = `${nombre} × ${it.cantidad}`;
const btn = document.createElement("button");
btn.textContent = "Quitar";
btn.onclick = () => {
items.splice(idx, 1);
renderItems();
};
li.appendChild(document.createTextNode(" "));
li.appendChild(btn);
itemsList.appendChild(li);
});
}
function mostrarMensaje(obj) {
try {
salida.textContent = typeof obj === "string" ? obj : JSON.stringify(obj, null, 2);
} catch {
salida.textContent = String(obj);
}
}
function validarEnteroPositivo(valor) {
const n = Number(valor);
return Number.isInteger(n) && n > 0;
}
// === LOGICA ===
async function cargarProductos() {
try {
const res = await fetch(API_BASE + PRODUCTOS_PATH);
if (!res.ok) throw new Error("No se pudieron obtener los productos");
const data = await res.json();
// Espera un array de productos con al menos {id, nombre}
productoSelect.innerHTML = "";
data.forEach(p => {
productosCache.set(p.id, p);
const opt = document.createElement("option");
opt.value = p.id;
opt.textContent = p.nombre || p.name || `Producto ${p.id}`;
productoSelect.appendChild(opt);
});
if (data.length === 0) {
mostrarMensaje("No hay productos disponibles.");
}
} catch (e) {
mostrarMensaje("Error cargando productos: " + e.message);
}
}
agregarBtn.addEventListener("click", () => {
const prodId = Number(productoSelect.value);
const cant = Number(cantidadInput.value);
if (!validarEnteroPositivo(prodId)) {
return mostrarMensaje("Selecciona un producto válido.");
}
if (!validarEnteroPositivo(cant)) {
return mostrarMensaje("La cantidad debe ser un entero positivo.");
}
// Si ya existe el producto en la lista, acumula cantidad
const existente = items.find(i => i.producto_id === prodId);
if (existente) {
existente.cantidad += cant;
} else {
items.push({ producto_id: prodId, cantidad: cant });
}
renderItems();
cantidadInput.value = 1;
mostrarMensaje("Producto agregado.");
});
enviarBtn.addEventListener("click", async () => {
const mesa_id = Number(mesaIdInput.value);
const mozo_id = mozoIdInput.value.trim();
const notas = notasInput.value.trim();
if (!validarEnteroPositivo(mesa_id)) {
return mostrarMensaje("Debes indicar un número de mesa válido.");
}
if (!mozo_id) {
return mostrarMensaje("Debes indicar el ID del mozo.");
}
if (items.length === 0) {
return mostrarMensaje("Agrega al menos un producto a la comanda.");
}
const payload = { mesa_id, mozo_id, notas, items };
enviarBtn.disabled = true;
mostrarMensaje("Enviando comanda...");
try {
const res = await fetch(API_BASE + COMANDAS_PATH, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload)
});
const data = await res.json();
if (!res.ok) {
mostrarMensaje({ error: "No se pudo crear la comanda", detalle: data });
} else {
mostrarMensaje({ ok: true, comanda: data });
// Limpia el estado
items.length = 0;
renderItems();
notasInput.value = "";
}
} catch (e) {
mostrarMensaje("Error al enviar comanda: " + e.message);
} finally {
enviarBtn.disabled = false;
}
});
// Cargar productos al iniciar
cargarProductos();
</script>
</body>
</html>
-43
View File
@@ -1,43 +0,0 @@
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>Dashboard</title>
</head>
<body>
<h2>Mesas</h2>
<select id="mesasSelect"></select>
<h2>Productos</h2>
<select id="productosSelect"></select>
<h2>Categorías</h2>
<select id="categoriasSelect"></select>
<h2>Comandas</h2>
<select id="comandasSelect"></select>
<script>
async function cargarDatos(endpoint, selectId, mostrar) {
const res = await fetch(endpoint); // Usar endpoint relativo
const data = await res.json();
const select = document.getElementById(selectId);
data.forEach(item => {
const option = document.createElement('option');
option.value = item.id;
option.textContent = mostrar(item);
select.appendChild(option);
});
}
// Al cargar la página, cargamos los datos
window.onload = () => {
cargarDatos('api/obtenerMesas', 'mesasSelect', mesa => `Mesa ${mesa.numero}`);
cargarDatos('api/obtenerProductos', 'productosSelect', prod => `${prod.nombre} ($${prod.precio})`);
cargarDatos('api/obtenerCategorias', 'categoriasSelect', cat => cat.nombre);
cargarDatos('api/obtenerComandas', 'comandasSelect', com => `Comanda ${com.id} - Mesa ${com.mesa_id} - $${com.total}`);
};
</script>
</body>
</html>
+633
View File
@@ -0,0 +1,633 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import argparse
import datetime
import json
import os
import pathlib
import shlex
import subprocess
import sys
from typing import List, Dict, Tuple, Optional, Set
PROJECT_ROOT = pathlib.Path.cwd()
COMPOSE_BASE = PROJECT_ROOT / "compose.yaml"
COMPOSE_DEV = PROJECT_ROOT / "compose.dev.yaml"
COMPOSE_PROD = PROJECT_ROOT / "compose.prod.yaml"
COMPOSE_NPM = PROJECT_ROOT / "compose.npm.yaml"
COMPOSE_DBVR = PROJECT_ROOT / "compose.dbeaver.yaml"
GLOBAL_DEFAULT_PROJECT = "suitecoffee" # proyecto global (NPM/DBeaver)
# ---------- Shell utils ----------
def run(cmd: List[str], check=True, capture_output=True, text=True) -> subprocess.CompletedProcess:
return subprocess.run(cmd, check=check, capture_output=capture_output, text=text)
def which(program: str) -> bool:
from shutil import which as _which
return _which(program) is not None
# ---------- Docker volume discovery ----------
def docker_volume_ls_json(filters: List[str]) -> List[Dict[str, str]]:
"""
Devuelve objetos de 'docker volume ls' (formato json por entrada).
Soporta filtros como '--filter label=...'.
"""
cmd = ["docker", "volume", "ls", "--format", "{{json .}}"]
for f in filters:
cmd += ["--filter", f]
try:
cp = run(cmd)
except subprocess.CalledProcessError:
return []
out = []
for line in cp.stdout.splitlines():
line = line.strip()
if not line:
continue
try:
out.append(json.loads(line))
except json.JSONDecodeError:
pass
return out
def docker_volume_ls_names(filters: List[str]) -> List[str]:
"""Devuelve solo los nombres (Name) con filtros aplicados."""
rows = docker_volume_ls_json(filters)
names = []
for v in rows:
name = v.get("Name")
if name:
names.append(name)
return names
def list_by_label_project(project: str) -> List[Dict[str, str]]:
return docker_volume_ls_json([f"label=com.docker.compose.project={project}"])
def list_by_name_prefix(prefix: str) -> List[Dict[str, str]]:
vols = docker_volume_ls_json([])
keep = []
for v in vols:
name = v.get("Name")
if not name:
continue
if name.startswith(prefix + "_") or name.startswith(prefix + "-") or name == prefix:
keep.append(v)
return keep
def normalize_project_name(p: str) -> str:
return (p or "").replace(" ", "_")
# ---------- Compose config parsing ----------
def compose_config_json(files: List[pathlib.Path]) -> Optional[dict]:
if not files or not all(p.exists() for p in files):
return None
cmd = ["docker", "compose"]
for f in files:
cmd += ["-f", str(f)]
cmd += ["config", "--format", "json"]
try:
cp = run(cmd)
return json.loads(cp.stdout or "{}")
except Exception:
return None
def extract_short_volume_names_from_config(cfg: dict) -> Set[str]:
"""
Extrae short names de volúmenes usados en services[].volumes (type: volume)
y las claves del toplevel 'volumes'.
"""
names: Set[str] = set()
if not cfg:
return names
# services[].volumes
services = cfg.get("services") or {}
for svc in services.values():
vols = svc.get("volumes") or []
for m in vols:
# en JSON canonical, cada mount es un dict con 'type', 'source', 'target', ...
if isinstance(m, dict) and m.get("type") == "volume":
src = m.get("source")
if isinstance(src, str) and src:
names.add(src)
# top-level volumes (claves)
top_vols = cfg.get("volumes") or {}
if isinstance(top_vols, dict):
for k in top_vols.keys():
if isinstance(k, str) and k:
names.add(k)
return names
def docker_compose_name_from(files: List[pathlib.Path]) -> Optional[str]:
cfg = compose_config_json(files)
if cfg and isinstance(cfg, dict):
name = cfg.get("name")
if name:
return name
return None
def read_compose_project_from_env(env_path: pathlib.Path) -> Optional[str]:
try:
if env_path.exists():
for line in env_path.read_text(encoding="utf-8").splitlines():
line = line.strip()
if not line or line.startswith("#"):
continue
if line.startswith("COMPOSE_PROJECT_NAME="):
return line.split("=", 1)[1].strip()
except Exception:
pass
return None
def base_folder_slug() -> str:
return PROJECT_ROOT.name.lower().replace(" ", "_")
def candidates_for_env(env: str) -> List[str]:
cand: List[str] = []
if env == "development":
n1 = docker_compose_name_from([COMPOSE_BASE, COMPOSE_DEV])
n2 = read_compose_project_from_env(PROJECT_ROOT / ".env.development")
n3 = f"{base_folder_slug()}_dev"
n4 = f"{base_folder_slug()}-dev"
cand.extend([n1, n2, n3, n4, base_folder_slug()])
elif env == "production":
n1 = docker_compose_name_from([COMPOSE_BASE, COMPOSE_PROD])
n2 = read_compose_project_from_env(PROJECT_ROOT / ".env.production")
n3 = f"{base_folder_slug()}_prod"
n4 = f"{base_folder_slug()}-prod"
cand.extend([n1, n2, n3, n4, base_folder_slug()])
# dedup preservando orden
seen = set(); ordered = []
for x in cand:
if x and x not in seen:
seen.add(x); ordered.append(x)
return ordered
def candidates_for_global() -> List[str]:
cand: List[str] = []
# nombres desde compose globales
if COMPOSE_NPM.exists():
n = docker_compose_name_from([COMPOSE_NPM])
if n: cand.append(n)
if COMPOSE_DBVR.exists():
n = docker_compose_name_from([COMPOSE_DBVR])
if n and n not in cand: cand.append(n)
# fallback esperados
if GLOBAL_DEFAULT_PROJECT not in cand: cand.append(GLOBAL_DEFAULT_PROJECT)
bf = base_folder_slug()
if bf not in cand: cand.append(bf)
return cand
# ---------- Nueva detección por grupo: COMPOSE + labels ----------
def detect_group_volumes_with_compose(filesets: List[List[pathlib.Path]],
project_candidates: List[str]) -> Tuple[Optional[str], str, List[str]]:
"""
filesets: lista de listas de archivos compose a considerar (dev=[base,dev], prod=[base,prod],
global=[[npm], [dbeaver]] -> dos sets para unir shortnames).
Devuelve (project_seleccionado, metodo, [nombres_de_volumen]).
"""
# 1) Unir shortnames de todos los filesets
shortnames: Set[str] = set()
for files in filesets:
cfg = compose_config_json(files)
shortnames |= extract_short_volume_names_from_config(cfg)
# 2) Si hay shortnames, probar a buscar por (project,label.volume)
if shortnames:
for proj in project_candidates:
# Buscar cada shortname con ambos labels
found: List[str] = []
for sn in sorted(shortnames):
names = docker_volume_ls_names([
f"label=com.docker.compose.project={proj}",
f"label=com.docker.compose.volume={sn}"
])
if names:
found.extend(names)
# dedup preservando orden
if found:
seen = set(); ordered = []
for n in found:
if n not in seen:
seen.add(n); ordered.append(n)
return proj, f"compose+labels:{proj}", ordered
# 3) Fallback: probar cualquier volumen del proyecto (label) o por prefijo
for proj in project_candidates:
method, rows = discover_volumes_for_project(proj)
if rows:
return proj, f"fallback:{method}", [r.get("Name") for r in rows if r.get("Name")]
# 4) Nada
first = project_candidates[0] if project_candidates else None
return first, "none", []
def discover_volumes_for_project(project_raw: str) -> Tuple[str, List[Dict[str, str]]]:
"""
Método previo de respaldo: por label de proyecto y prefijo (para CLI y fallback).
"""
project_norm = normalize_project_name(project_raw)
project_lower = project_norm.lower()
vols = list_by_label_project(project_norm)
if vols:
return f"label:{project_norm}", vols
vols2 = list_by_label_project(project_lower)
if vols2:
return f"label:{project_lower}", vols2
by_name = list_by_name_prefix(project_norm)
if by_name:
return f"name-prefix:{project_norm}", by_name
by_name2 = list_by_name_prefix(project_lower)
if by_name2:
return f"name-prefix:{project_lower}", by_name2
return "none", []
# ---------- Backup helpers ----------
def ensure_alpine_image():
try:
run(["docker", "image", "inspect", "alpine:latest"])
except subprocess.CalledProcessError:
print("Pulling alpine:latest ...")
run(["docker", "pull", "alpine:latest"], check=True, capture_output=False)
def build_archive_name(project: str, volume_name: str, ts: str) -> str:
"""
Construye el nombre del .tar.gz evitando duplicar el prefijo del proyecto.
- Si volume_name ya empieza con '<project>_' o '<project>-', se usa tal cual.
- Si no, se antepone '<project>_'.
Resultado: <project>_<shortname>-<ts>.tar.gz
"""
proj_token = project.lower().replace(" ", "_")
v_lower = volume_name.lower()
if v_lower.startswith(proj_token + "_") or v_lower.startswith(proj_token + "-"):
base = volume_name
else:
base = f"{proj_token}_{volume_name}"
return f"{base}-{ts}.tar.gz"
def backup_volume(volume_name: str, out_dir: pathlib.Path, archive_name: str, dry_run: bool = False) -> int:
out_dir.mkdir(parents=True, exist_ok=True)
out_dir_abs = out_dir.resolve()
out_path = out_dir_abs / archive_name
docker_cmd = [
"docker", "run", "--rm",
"-v", f"{volume_name}:/volume:ro",
"-v", f"{str(out_dir_abs)}:/backup",
# "--user", f"{os.getuid()}:{os.getgid()}",
"alpine:latest",
"sh", "-lc",
f"tar czf /backup/{shlex.quote(out_path.name)} -C /volume ."
]
if dry_run:
print("[DRY RUN] Would run:", " ".join(shlex.quote(c) for c in docker_cmd))
return 0
cp = subprocess.run(docker_cmd)
return cp.returncode
def backup_explicit(volume_names: List[str], ts: str, output_dir: Optional[str], dry_run: bool, prefix_project: Optional[str]) -> int:
"""
Respalda exactamente los volúmenes indicados.
- Directorio por defecto: ./docker-volumes-<ts>
- Nombre de archivo: build_archive_name(prefix_project, volume_name, ts)
"""
out_dir = pathlib.Path(output_dir) if output_dir else (PROJECT_ROOT / f"docker-volumes-{ts}")
if not dry_run:
ensure_alpine_image()
failures = []
for vname in volume_names:
if not vname:
continue
archive = build_archive_name(prefix_project or "", vname, ts)
print(f"Backing up volume: {vname} -> {archive}")
rc = backup_volume(vname, out_dir, archive, dry_run=dry_run)
if rc != 0:
print(f" ERROR: backup failed for volume '{vname}' (exit code {rc})", file=sys.stderr)
failures.append(vname)
if failures:
print("\nCompleted with errors. Failed volumes:", ", ".join(failures))
return 1
else:
print("\nAll done. Archives written to:", str(out_dir.resolve()))
return 0
def backup_group(project_name: str, ts: str, output_dir: Optional[str] = None,
dry_run: bool = False, excludes: Optional[List[str]] = None) -> int:
"""
Fallback legacy (label/prefix). Mantiene coherencia con nombres y directorio por defecto.
"""
method, rows = discover_volumes_for_project(project_name)
print_header(f"Proyecto '{project_name}': {len(rows)} volumen(es) detectado(s) (método: {method})")
for v in rows:
print(" -", v.get("Name"))
if not rows:
warn("No hay volúmenes para respaldar.")
return 0
vols = [v.get("Name") for v in rows if v.get("Name")]
if excludes:
excl = set(excludes)
vols = [n for n in vols if n not in excl]
if not vols:
warn("Tras aplicar exclusiones, no quedó nada por respaldar.")
return 0
out_dir = pathlib.Path(output_dir) if output_dir else (PROJECT_ROOT / f"docker-volumes-{ts}")
if not dry_run:
ensure_alpine_image()
failures = []
for vname in vols:
archive = build_archive_name(project_name, vname, ts)
print(f"Backing up volume: {vname} -> {archive}")
rc = backup_volume(vname, out_dir, archive, dry_run=dry_run)
if rc != 0:
print(f" ERROR: backup failed for volume '{vname}' (exit code {rc})", file=sys.stderr)
failures.append(vname)
if failures:
print("\nCompleted with errors. Failed volumes:", ", ".join(failures))
return 1
else:
print("\nAll done. Archives written to:", str(out_dir.resolve()))
return 0
# ---------- UI helpers ----------
def yes_no(prompt: str, default="n") -> bool:
default = default.lower()
hint = "[Y/n]" if default == "y" else "[y/N]"
while True:
resp = input(f"{prompt} {hint} ").strip().lower()
if not resp:
return default == "y"
if resp in ("y","yes","s","si",""):
return True
if resp in ("n","no"):
return False
print("Respuesta no reconocida. Por favor, responde con 'y' o 'n'.")
def print_header(title: str):
print("\n" + "=" * 60)
print(title)
print("=" * 60 + "\n")
def info(msg): print(f"{msg}")
def ok(msg): print(f"{msg}")
def warn(msg): print(f"! {msg}")
def fail(msg):
print(f"{msg}")
sys.exit(1)
# ---------- Menú interactivo ----------
def interactive_menu():
if not which("docker"):
fail("ERROR: 'docker' no está en el PATH.")
try:
run(["docker", "version"], check=True, capture_output=True)
except subprocess.CalledProcessError:
fail("ERROR: No se puede hablar con el daemon de Docker. ¿Está corriendo? ¿Tu usuario está en el grupo 'docker'?")
# DEV
dev_candidates = candidates_for_env("development") if COMPOSE_BASE.exists() and COMPOSE_DEV.exists() else []
dev_proj, dev_method, dev_names = detect_group_volumes_with_compose(
filesets=[[COMPOSE_BASE, COMPOSE_DEV]] if dev_candidates else [],
project_candidates=dev_candidates
)
# PROD
prod_candidates = candidates_for_env("production") if COMPOSE_BASE.exists() and COMPOSE_PROD.exists() else []
prod_proj, prod_method, prod_names = detect_group_volumes_with_compose(
filesets=[[COMPOSE_BASE, COMPOSE_PROD]] if prod_candidates else [],
project_candidates=prod_candidates
)
# GLOBAL = NPM + DBEAVER (unir shortnames de ambos)
global_candidates = candidates_for_global()
global_filesets = []
if COMPOSE_NPM.exists():
global_filesets.append([COMPOSE_NPM])
if COMPOSE_DBVR.exists():
global_filesets.append([COMPOSE_DBVR])
glob_proj, glob_method, glob_names = detect_group_volumes_with_compose(
filesets=global_filesets,
project_candidates=global_candidates
)
# Resumen
print_header("Resumen de volúmenes detectados")
if dev_proj:
info(f"DESARROLLO ({dev_proj}): {len(dev_names)} volumen(es) (método: {dev_method})")
else:
info("DESARROLLO: archivos compose no encontrados.")
if prod_proj:
info(f"PRODUCCIÓN ({prod_proj}): {len(prod_names)} volumen(es) (método: {prod_method})")
else:
info("PRODUCCIÓN: archivos compose no encontrados.")
if glob_proj:
info(f"GLOBALES ({glob_proj}): {len(glob_names)} volumen(es) (método: {glob_method})")
else:
info("GLOBALES: no se detectaron archivos compose globales.")
print()
# Menú
options = {}
key = 1
if dev_proj:
print(f" {key}) Respaldar volúmenes de DESARROLLO ({dev_proj})")
options[str(key)] = ("backup_explicit", dev_proj, dev_names); key += 1
if prod_proj:
print(f" {key}) Respaldar volúmenes de PRODUCCIÓN ({prod_proj})")
options[str(key)] = ("backup_explicit", prod_proj, prod_names); key += 1
if glob_proj:
print(f" {key}) Respaldar volúmenes GLOBALES ({glob_proj})")
options[str(key)] = ("backup_explicit", glob_proj, glob_names); key += 1
# TODOS: unión deduplicada por nombre (respalda 1 vez cada volumen)
groups = []
if dev_proj: groups.append( (dev_proj, dev_names) )
if prod_proj: groups.append( (prod_proj, prod_names) )
if glob_proj: groups.append( (glob_proj, glob_names) )
if len(groups) >= 2:
print(f" {key}) Respaldar TODOS los grupos detectados")
options[str(key)] = ("backup_all_explicit", groups); key += 1
print(f" {key}) Salir")
exit_key = str(key)
ts = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
while True:
choice = input("> ").strip()
if choice == exit_key:
ok("Saliendo.")
sys.exit(0)
if choice not in options:
print("Opción inválida.")
continue
action = options[choice][0]
dry = yes_no("¿Dry-run (no escribir archivos)?", default="n")
outd = input(f"Directorio de salida (vacío = ./docker-volumes-{ts}): ").strip() or None
excl_input = input("Excluir volúmenes (nombres separados por coma, vacío = ninguno): ").strip()
excludes = set(e.strip() for e in excl_input.split(",") if e.strip()) if excl_input else set()
if action == "backup_explicit":
_, proj, names = options[choice]
names = [n for n in names if n not in excludes]
if not names:
warn("No hay volúmenes para respaldar.")
sys.exit(0)
rc = backup_explicit(names, ts, output_dir=outd, dry_run=dry, prefix_project=proj)
sys.exit(rc)
elif action == "backup_all_explicit":
_, groups_payload = options[choice]
vol_to_proj: Dict[str, str] = {}
for proj, names in groups_payload:
for n in names:
if n not in excludes and n not in vol_to_proj:
vol_to_proj[n] = proj
if not vol_to_proj:
warn("No hay volúmenes para respaldar.")
sys.exit(0)
if not dry:
ensure_alpine_image()
out_dir = pathlib.Path(outd) if outd else (PROJECT_ROOT / f"docker-volumes-{ts}")
failures = []
for vname, proj in vol_to_proj.items():
archive = build_archive_name(proj, vname, ts)
print(f"Backing up volume: {vname} -> {archive}")
rc = backup_volume(vname, out_dir, archive, dry_run=dry)
if rc != 0:
print(f" ERROR: backup failed for volume '{vname}' (exit code {rc})", file=sys.stderr)
failures.append(vname)
if failures:
print("\nCompleted with errors. Failed volumes:", ", ".join(failures))
sys.exit(1)
else:
print("\nAll done. Archives written to:", str(out_dir.resolve()))
sys.exit(0)
# ---------- CLI legacy (se mantiene) ----------
def detect_project_name(args_project: Optional[str]) -> str:
if args_project:
return args_project
env_name = os.environ.get("COMPOSE_PROJECT_NAME")
if env_name:
return env_name
return PROJECT_ROOT.name.replace(" ", "_")
def cli_main():
parser = argparse.ArgumentParser(description="Export (compress) every Docker volume of a Docker Compose project.")
parser.add_argument("-p", "--project", help="Compose project or prefix (see --discovery).")
parser.add_argument("-o", "--output", help="Output directory (default: ./docker-volumes-<timestamp>).")
parser.add_argument("--exclude", nargs="*", default=[], help="Volume names to exclude (space-separated).")
parser.add_argument("--dry-run", action="store_true", help="Show what would be done without doing it.")
parser.add_argument("--timestamp", default=datetime.datetime.now().strftime("%Y%m%d-%H%M%S"),
help="Timestamp to embed into filenames (default: current time).")
parser.add_argument("--discovery", choices=["auto","label","name"], default="auto",
help="How to discover volumes: 'label' (strict), 'name' (prefix), or 'auto' (default).")
parser.add_argument("--list-only", action="store_true", help="Only list volumes that would be backed up and exit.")
parser.add_argument("--menu", action="store_true", help="Launch interactive menu instead of CLI behavior.")
args = parser.parse_args()
if args.menu or not args.project:
interactive_menu()
return
if not which("docker"):
print("ERROR: 'docker' not on PATH.", file=sys.stderr)
sys.exit(2)
project_raw = detect_project_name(args.project)
project_norm = normalize_project_name(project_raw)
project_lower = project_norm.lower()
ts = args.timestamp
out_dir = pathlib.Path(args.output) if args.output else (PROJECT_ROOT / f"docker-volumes-{ts}")
try:
run(["docker", "version"], check=True, capture_output=True)
except subprocess.CalledProcessError:
print("ERROR: Docker daemon not reachable.", file=sys.stderr)
sys.exit(2)
# Descubrimiento legacy por label/prefijo (se mantiene para compatibilidad)
selected = []
method_used = None
vols = list_by_label_project(project_norm)
if vols:
selected = vols; method_used = f"label:{project_norm}"
else:
vols2 = list_by_label_project(project_lower)
if vols2:
selected = vols2; method_used = f"label:{project_lower}"
if not selected:
by_name = list_by_name_prefix(project_norm)
if by_name:
selected = by_name; method_used = f"name-prefix:{project_norm}"
else:
by_name2 = list_by_name_prefix(project_lower)
if by_name2:
selected = by_name2; method_used = f"name-prefix:{project_lower}"
if not selected:
print(f"No volumes found for project/prefix '{project_raw}'.")
sys.exit(0)
exclude_set = set(args.exclude or [])
names = [v.get("Name") for v in selected if v.get("Name") not in exclude_set]
print(f"Discovery method: {method_used}")
print(f"Volumes discovered: {len(names)}")
for n in names:
print(" -", n)
if args.list_only:
return
if not args.dry_run:
ensure_alpine_image()
failures = []
for vname in names:
archive = build_archive_name(project_lower, vname, ts)
print(f"Backing up volume: {vname} -> {archive}")
rc = backup_volume(vname, out_dir, archive, dry_run=args.dry_run)
if rc != 0:
print(f" ERROR: backup failed for volume '{vname}' (exit code {rc})", file=sys.stderr)
failures.append(vname)
if failures:
print("\nCompleted with errors. Failed volumes:", ", ".join(failures))
sys.exit(1)
else:
print("\nAll done. Archives written to:", str(out_dir.resolve()))
# ---------- Entry point ----------
if __name__ == "__main__":
if len(sys.argv) == 1:
interactive_menu()
else:
cli_main()
+73
View File
@@ -0,0 +1,73 @@
# docker-compose.overrride.yml
# Docker Comose para entorno de desarrollo o development.
services:
app:
image: node:20-bookworm
expose:
- ${APP_LOCAL_PORT}
working_dir: /app
user: "${UID:-1000}:${GID:-1000}"
volumes:
- ./services/app:/app:rw
- ./services/app/node_modules:/app/node_modules
env_file:
- ./services/app/.env.development
environment:
- NODE_ENV=${NODE_ENV}
networks:
net:
aliases: [dev-app]
command: npm run dev
auth:
image: node:20-bookworm
expose:
- ${AUTH_LOCAL_PORT}
working_dir: /app
user: "${UID:-1000}:${GID:-1000}"
volumes:
- ./services/auth:/app:rw
- ./services/auth/node_modules:/app/node_modules
env_file:
- ./services/auth/.env.development
environment:
- NODE_ENV=${NODE_ENV}
command: npm run dev
networks:
net:
aliases: [dev-auth]
db:
image: postgres:16
environment:
POSTGRES_DB: ${DB_NAME}
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: ${DB_PASS}
volumes:
- suitecoffee-db:/var/lib/postgresql/data
networks:
net:
aliases: [dev-db]
tenants:
image: postgres:16
environment:
POSTGRES_DB: ${TENANTS_DB_NAME}
POSTGRES_USER: ${TENANTS_DB_USER}
POSTGRES_PASSWORD: ${TENANTS_DB_PASS}
volumes:
- tenants-db:/var/lib/postgresql/data
networks:
net:
aliases: [dev-tenants]
volumes:
tenants-db:
suitecoffee-db:
networks:
net:
driver: bridge
+71
View File
@@ -0,0 +1,71 @@
# compose.prod.yml
# Docker Comose para entorno de producción o production.
services:
app:
build:
context: ./services/app
dockerfile: Dockerfile.production
expose:
- ${APP_LOCAL_PORT}
volumes:
- ./services/app:/app
env_file:
- ./services/app/.env.production
environment:
- NODE_ENV=${NODE_ENV}
networks:
net:
aliases: [prod-app]
command: npm run start
auth:
build:
context: ./services/auth
dockerfile: Dockerfile.production
expose:
- ${AUTH_LOCAL_PORT}
volumes:
- ./services/auth:/app
env_file:
- ./services/auth/.env.production
environment:
- NODE_ENV=${NODE_ENV}
command: npm run start
networks:
net:
aliases: [prod-auth]
db:
image: postgres:16
environment:
POSTGRES_DB: ${DB_NAME}
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: ${DB_PASS}
volumes:
- suitecoffee-db:/var/lib/postgresql/data
networks:
net:
aliases: [prod-db]
tenants:
image: postgres:16
environment:
POSTGRES_DB: ${TENANTS_DB_NAME}
POSTGRES_USER: ${TENANTS_DB_USER}
POSTGRES_PASSWORD: ${TENANTS_DB_PASS}
volumes:
- tenants-db:/var/lib/postgresql/data
networks:
net:
aliases: [prod-tenants]
volumes:
tenants-db:
suitecoffee-db:
networks:
net:
driver: bridge
+61
View File
@@ -0,0 +1,61 @@
# compose.tools.yaml
name: suitecoffee_tools
services:
dbeaver:
image: dbeaver/cloudbeaver:latest
profiles: [dbeaver]
ports:
- 8978:8978
environment:
TZ: America/Montevideo
volumes:
- dbeaver_logs:/opt/cloudbeaver/logs
- dbeaver_workspace:/opt/cloudbeaver/workspace
networks:
suitecoffee_prod_net: {}
suitecoffee_dev_net: {}
healthcheck:
test: ["CMD-SHELL", "curl -fsS http://localhost:8978 || exit 1"]
interval: 10s
timeout: 3s
retries: 10
start_period: 20s
restart: unless-stopped
npm:
image: jc21/nginx-proxy-manager:latest
profiles: [npm]
restart: unless-stopped
ports:
- "80:80" # HTTP público
- "81:81" # UI de administración
environment:
TZ: America/Montevideo
volumes:
- npm_data:/data
- npm_letsencrypt:/etc/letsencrypt
networks:
suitecoffee_prod_net: {}
suitecoffee_dev_net: {}
healthcheck:
test: ["CMD-SHELL", "curl -fsS http://localhost:81 || exit 1"]
interval: 10s
timeout: 3s
retries: 10
start_period: 20s
networks:
suitecoffee_dev_net:
external: true
suitecoffee_prod_net:
external: true
volumes:
npm_data:
npm_letsencrypt:
dbeaver_logs:
dbeaver_workspace:
+55
View File
@@ -0,0 +1,55 @@
# compose.yml
# Comose base
name: ${COMPOSE_PROJECT_NAME:-suitecoffee}
services:
# app:
# depends_on:
# db:
# condition: service_healthy
# tenants:
# condition: service_healthy
# healthcheck:
# test: ["CMD-SHELL", "curl -fsS http://localhost:${APP_DOCKER_PORT}/health || exit 1"]
# interval: 10s
# timeout: 3s
# retries: 10
# start_period: 20s
# restart: unless-stopped
# auth:
# depends_on:
# db:
# condition: service_healthy
# healthcheck:
# test: ["CMD-SHELL", "curl -fsS http://localhost:${AUTH_DOCKER_PORT}/health || exit 1"]
# interval: 10s
# timeout: 3s
# retries: 10
# start_period: 15s
# restart: unless-stopped
db:
image: postgres:16
environment:
TZ: America/Montevideo
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USER} -d ${DB_NAME}"]
interval: 5s
timeout: 3s
retries: 20
start_period: 10s
restart: unless-stopped
tenants:
image: postgres:16
environment:
TZ: America/Montevideo
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${TENANTS_DB_USER} -d ${TENANTS_DB_NAME}"]
interval: 5s
timeout: 3s
retries: 20
start_period: 10s
restart: unless-stopped
-80
View File
@@ -1,80 +0,0 @@
# docker-compose.overrride.yml
# Docker Comose para entorno de desarrollo o development.
# <---- Estructura ---->
# services:
# suitecoffe-app
# suitecoffe-db
# auth-service (No implementado aún)
# auth-db (No implementado aún)
# volumes:
# pg-appdb-data:
# pg-authdb-data: (No implementado aún)
services:
suitecoffee-app:
container_name: suitecoffee-app
depends_on:
- suitecoffee-db
build:
context: ./services/app
dockerfile: Dockerfile.development
volumes:
- ./services/app:/app
ports:
- ${APP_LOCAL_PORT}:${APP_DOCKER_PORT}
env_file:
- ./services/app/.env.development
environment:
- NODE_ENV=${NODE_ENV}
command: npm run dev
restart: unless-stopped
suitecoffee-db:
image: postgres:16
container_name: suitecoffee-db
environment:
POSTGRES_DB: suitecoffee
POSTGRES_USER: suitecoffee
POSTGRES_PASSWORD: suitecoffee
ports:
- "54321:5432"
volumes:
- pg-appdb-data:/var/lib/postgresql/data
restart: unless-stopped
# auth-service:
# container_name: auth-service
# depends_on:
# - auth-db
# build:
# context: ./services/app
# dockerfile: Dockerfile.dev
# volumes:
# - ./services/auth:/app
# ports:
# - 3030:3030 # Usa la variable de entorno PORT
# # env_file:
# # - ./services/auth/.env.dev
# environment:
# # - NODE_ENV=dev
# - PORT=3030
# command: npm run dev # Usa nodemon para desarrollo (visitar package.json)
# restart: unless-stopped
# auth-db:
# container_name: auth-db
# image: postgres:16
# # ports:
# # - ${AUTH_DB_LOCAL_PORT}:${AUTH_DB_DOCKER_PORT}
# # env_file:
# # - ./services/auth/.env.dev
# environment:
# POSTGRES_DB: "auth"
# POSTGRES_USER: "dev_user"
# POSTGRES_PASSWORD: "dev_password"
volumes:
pg-appdb-data:
View File
-6
View File
@@ -1,6 +0,0 @@
@startuml Modelo Entidad-Relacion
class Producto {
- idProducto: int
+ getId(): int
}
@enduml
-2
View File
@@ -1,2 +0,0 @@
docker compose -f docker-compose.yml -f docker-compose.override.yml \
--env-file .env.development up -d
+21
View File
@@ -0,0 +1,21 @@
{
"name": "suitecoffee",
"version": "1.0.0",
"description": "Software para gestión de cafeterías",
"keywords": [
"coffee",
"suite",
"suitecoffee"
],
"repository": {
"type": "git",
"url": "https://gitea.mateosaldain.uy/msaldain/SuiteCoffee.git"
},
"license": "ISC",
"author": "Mateo Saldain",
"type": "module",
"main": "suitecoffee.py",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
}
}
+466
View File
@@ -0,0 +1,466 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
restore_compose_volumes.py
--------------------------
Restaura volúmenes desde backups generados por backup_compose_volumes.py.
- Busca carpetas ./docker-volumes-<timestamp>
- Lee .tar.gz (nombres: <volume_name>-<YYYYMMDD-HHMMSS>.tar.gz)
- Dos modos:
1) Tradicional (sin labels)
2) Reconocido por Compose (aplica labels com.docker.compose.* para evitar el warning)
Además:
- Si un volumen existe y está en uso, ofrece detener y eliminar contenedores que lo usan
para poder recrearlo con labels correctos (solo en modo 2).
"""
import os
import re
import sys
import json
import shlex
import pathlib
import subprocess
from typing import List, Tuple, Dict, Optional
PROJECT_ROOT = pathlib.Path.cwd()
BACKUP_DIR_PATTERN = re.compile(r"^docker-volumes-\d{8}-\d{6}$")
ARCHIVE_PATTERN = re.compile(r"^(?P<basename>.+)-(?P<ts>\d{8}-\d{6})\.tar\.gz$")
# ---------- utils ----------
def run(cmd: List[str], check: bool = False, capture_output: bool = True, text: bool = True) -> subprocess.CompletedProcess:
return subprocess.run(cmd, check=check, capture_output=capture_output, text=text)
def which(prog: str) -> bool:
from shutil import which as _w
return _w(prog) is not None
def fail(msg: str):
print(f"{msg}")
sys.exit(1)
def ok(msg: str):
print(f"{msg}")
def info(msg: str):
print(f"{msg}")
def warn(msg: str):
print(f"! {msg}")
def yes_no(prompt: str, default: str = "n") -> bool:
default = default.lower()
hint = "[Y/n]" if default == "y" else "[y/N]"
while True:
resp = input(f"{prompt} {hint} ").strip().lower()
if not resp:
return default == "y"
if resp in ("y","yes","s","si",""):
return True
if resp in ("n","no"):
return False
print("Respuesta no reconocida. Responde 'y' o 'n'.")
# ---------- docker helpers ----------
def ensure_alpine_image():
try:
run(["docker", "image", "inspect", "alpine:latest"], check=True)
except subprocess.CalledProcessError:
info("Descargando alpine:latest ...")
run(["docker", "pull", "alpine:latest"], check=True, capture_output=False, text=True)
def volume_exists(name: str) -> bool:
try:
run(["docker", "volume", "inspect", name], check=True)
return True
except subprocess.CalledProcessError:
return False
def inspect_volume_labels(name: str) -> Dict[str, str]:
try:
cp = run(["docker", "volume", "inspect", name, "--format", "{{json .Labels}}"], check=True)
return json.loads(cp.stdout or "null") or {}
except subprocess.CalledProcessError:
return {}
def containers_using_volume(name: str) -> List[str]:
# docker ps soporta --filter volume=<name>
try:
cp = run(["docker", "ps", "-a", "--filter", f"volume={name}", "-q"], check=True)
return [l.strip() for l in cp.stdout.splitlines() if l.strip()]
except subprocess.CalledProcessError:
return []
def stop_containers(ids: List[str]) -> None:
if not ids:
return
info("Deteniendo contenedores que usan el volumen...")
run(["docker", "stop"] + ids, check=False, capture_output=False)
def remove_containers(ids: List[str]) -> None:
if not ids:
return
info("Eliminando contenedores detenidos que usan el volumen...")
run(["docker", "rm"] + ids, check=False, capture_output=False)
def remove_volume(name: str) -> bool:
try:
run(["docker", "volume", "rm", "-f", name], check=True, capture_output=False)
return True
except subprocess.CalledProcessError as e:
warn(f"No se pudo eliminar volumen '{name}': {e}")
return False
def create_volume(name: str, labels: Optional[Dict[str,str]] = None) -> bool:
cmd = ["docker", "volume", "create"]
if labels:
for k, v in labels.items():
cmd += ["--label", f"{k}={v}"]
cmd.append(name)
try:
run(cmd, check=True, capture_output=False)
return True
except subprocess.CalledProcessError as e:
warn(f"Fallo creando volumen '{name}': {e}")
return False
def restore_into_volume(volume_name: str, backup_dir: pathlib.Path, archive_file: pathlib.Path) -> int:
bdir_abs = backup_dir.resolve()
docker_cmd = [
"docker", "run", "--rm",
"-v", f"{volume_name}:/volume",
"-v", f"{str(bdir_abs)}:/backup",
"alpine:latest",
"sh", "-lc",
f"tar xzf /backup/{shlex.quote(archive_file.name)} -C /volume"
]
proc = subprocess.run(docker_cmd)
return proc.returncode
# ---------- parsing helpers ----------
def find_backup_dirs(root: pathlib.Path) -> List[pathlib.Path]:
dirs = [p for p in root.iterdir() if p.is_dir() and BACKUP_DIR_PATTERN.match(p.name)]
dirs.sort(key=lambda p: p.stat().st_mtime, reverse=True)
return dirs
def find_archives(dirpath: pathlib.Path) -> List[pathlib.Path]:
files = [p for p in dirpath.iterdir() if p.is_file() and p.name.endswith(".tar.gz")]
files.sort(key=lambda p: p.name)
return files
def parse_archive_basename(archive_name: str) -> Optional[str]:
m = ARCHIVE_PATTERN.match(archive_name)
if not m:
return None
return m.group("basename")
# ---------- compose label helpers ----------
def derive_labels_auto(volume_name: str) -> Tuple[Optional[str], Optional[str]]:
"""
project = parte antes del primer '_' o '-'
short = resto luego del separador
"""
for sep in ("_", "-"):
if sep in volume_name:
idx = volume_name.find(sep)
return volume_name[:idx], volume_name[idx+1:]
return None, None
def derive_labels_with_fixed_project(volume_name: str, project: str) -> Tuple[str, str]:
"""
Usa project fijo. Si volume_name empieza con '<project>_' o '<project>-', recorta.
"""
p = project
if volume_name.startswith(p + "_"):
return p, volume_name[len(p) + 1:]
if volume_name.startswith(p + "-"):
return p, volume_name[len(p) + 1:]
return p, volume_name
def labels_match_compose(name: str, project: str, short: str) -> bool:
labels = inspect_volume_labels(name)
return (
labels.get("com.docker.compose.project") == project and
labels.get("com.docker.compose.volume") == short
)
# ---------- UI flows ----------
def pick_backup_dir(dirs: List[pathlib.Path]) -> Optional[pathlib.Path]:
if not dirs:
warn("No se encontraron carpetas de backup 'docker-volumes-<timestamp>'.")
return None
print("\nCarpetas de backup encontradas:")
for i, d in enumerate(dirs, 1):
print(f" {i}) {d.name}")
while True:
sel = input("> Elige una carpeta (número) o Enter para cancelar: ").strip()
if not sel:
return None
if sel.isdigit() and 1 <= int(sel) <= len(dirs):
return dirs[int(sel) - 1]
print("Opción inválida.")
def pick_archives(files: List[pathlib.Path]) -> List[pathlib.Path]:
if not files:
warn("No hay archivos .tar.gz en esa carpeta.")
return []
print("\nBackups disponibles:")
for i, f in enumerate(files, 1):
base = parse_archive_basename(f.name) or f.name
print(f" {i}) {f.name} -> volumen: {base}")
print("\nOpciones:")
print(" a) Restaurar TODOS")
print(" s) Seleccionar algunos (ej: 1,3,5)")
while True:
sel = input("> Elige 'a' o 's': ").strip().lower()
if sel == "a":
return files
if sel == "s":
picks = input("> Números separados por coma: ").strip()
idxs = []
try:
for tok in picks.split(","):
tok = tok.strip()
if tok:
idx = int(tok)
idxs.append(idx - 1)
chosen = [files[i] for i in sorted(set(i for i in idxs if 0 <= i < len(files)))]
if chosen:
return chosen
except Exception:
pass
print("Selección inválida.")
else:
print("Opción inválida.")
def pick_restore_mode() -> str:
print("\nModo de restauración:")
print(" 1) Tradicional (sin labels)")
print(" 2) Reconocido por Compose (aplica labels para evitar el warning)")
while True:
sel = input("> Elige 1 o 2: ").strip()
if sel in ("1", "2"):
return sel
print("Opción inválida.")
def confirm_overwrite(volume_name: str) -> bool:
return yes_no(f"El volumen '{volume_name}' ya existe. ¿Sobrescribir (recrear)?", default="n")
# ---------- restore flows ----------
def restore_traditional(backup_dir: pathlib.Path, archives: List[pathlib.Path]):
ensure_alpine_image()
print("\n=== Restauración TRADICIONAL ===\n")
for arch in archives:
vname = parse_archive_basename(arch.name)
if not vname:
warn(f"Nombre de backup no reconocible: {arch.name}, se omite.")
continue
info(f"Volumen: {vname}")
# Tradicional: no cambiamos labels; si existe, restauramos sobre volumen nuevo (recreándolo)
if volume_exists(vname):
# Intentar eliminar: si está en uso, ofrecer detener/remover contenedores
if not confirm_overwrite(vname):
info(" → Omitido (ya existe).")
continue
ids = containers_using_volume(vname)
if ids:
info(f"Contenedores que usan '{vname}': {', '.join(ids)}")
if yes_no("¿Detener y eliminar esos contenedores para continuar?", default="y"):
stop_containers(ids)
remove_containers(ids)
else:
warn(" → No se puede recrear el volumen en uso. Omitido.")
continue
if not remove_volume(vname):
warn(" → No se pudo eliminar el volumen. Omitido.")
continue
if not create_volume(vname):
warn(" → No se pudo crear el volumen, se omite.")
continue
rc = restore_into_volume(vname, backup_dir, arch)
if rc == 0:
ok(" Restaurado.")
else:
warn(f" Falló la restauración (rc={rc}).")
def restore_with_compose_labels(backup_dir: pathlib.Path, archives: List[pathlib.Path]):
"""
Restaura creando volúmenes con labels de Compose para que NO aparezca el warning:
"volume ... already exists but was not created by Docker Compose..."
"""
ensure_alpine_image()
print("\n=== Restauración RECONOCIDA POR COMPOSE (con labels) ===\n")
print("Estrategia de etiquetado:")
print(" 1) Auto (project = prefijo de <vol> antes de '_' o '-', short = resto)")
print(" 2) Fijar un 'project' para todos (p. ej. suitecoffee, suitecoffee_dev, suitecoffee_prod)")
mode = ""
while mode not in ("1", "2"):
mode = input("> Elige 1 o 2: ").strip()
fixed_project = None
if mode == "2":
fixed_project = input("> Indica el 'project' de Compose (exacto): ").strip()
if not fixed_project:
warn("Project vacío, cancelado.")
return
# Previsualización de etiquetas
preview = []
for arch in archives:
vname = parse_archive_basename(arch.name)
if not vname:
continue
if mode == "1":
proj, short = derive_labels_auto(vname)
else:
proj, short = derive_labels_with_fixed_project(vname, fixed_project)
preview.append((arch, vname, proj, short))
print("\nVista previa de etiquetas (project / volume):")
for _, vname, proj, short in preview:
if proj and short:
print(f" {vname} → project='{proj}', volume='{short}'")
else:
print(f" {vname} → (no derivado; se pedirá manualmente)")
if not yes_no("\n¿Confirmar restauración con estas etiquetas?", default="y"):
warn("Cancelado por el usuario.")
return
# Restaurar con labels
for arch, vname, proj, short in preview:
# completar manual si falta
if not proj or not short:
print(f"\nDefinir etiquetas para: {vname}")
proj = input(" project = ").strip()
short = input(" volume = ").strip()
if not proj or not short:
warn(" → Etiquetas incompletas; se omite.")
continue
info(f"\nVolumen: {vname} (labels: project='{proj}', volume='{short}')")
if volume_exists(vname):
# ¿ya tiene labels correctas? entonces solo restauramos datos sin recrear
if labels_match_compose(vname, proj, short):
info(" Volumen ya tiene labels de Compose correctas. Sobrescribiendo datos...")
rc = restore_into_volume(vname, backup_dir, arch)
if rc == 0:
ok(" Restaurado (labels ya correctas).")
else:
warn(f" Falló la restauración (rc={rc}).")
continue
# Pedir permiso para detener/eliminar contenedores y recrear volumen con labels correctas
if not yes_no(" El volumen existe sin labels correctas. ¿Detener/eliminar contenedores y recrearlo con labels para evitar el warning?", default="y"):
warn(" → Omitido (mantiene warning de Compose).")
continue
ids = containers_using_volume(vname)
if ids:
info(f" Contenedores que usan '{vname}': {', '.join(ids)}")
stop_containers(ids)
remove_containers(ids)
if not remove_volume(vname):
warn(" → No se pudo eliminar el volumen. Omitido.")
continue
labels = {
"com.docker.compose.project": proj,
"com.docker.compose.volume": short,
}
if not create_volume(vname, labels=labels):
warn(" → No se pudo crear el volumen con labels. Omitido.")
continue
rc = restore_into_volume(vname, backup_dir, arch)
if rc == 0:
ok(" Restaurado con labels de Compose (warning resuelto).")
else:
warn(f" Falló la restauración (rc={rc}).")
# ---------- main ----------
def main():
if not which("docker"):
fail("No se encontró 'docker' en el PATH.")
try:
run(["docker", "version"], check=True)
except subprocess.CalledProcessError:
fail("No se puede comunicar con el daemon de Docker. ¿Está corriendo?")
# Elegir carpeta docker-volumes-<ts>
dirs = [p for p in PROJECT_ROOT.iterdir() if p.is_dir() and BACKUP_DIR_PATTERN.match(p.name)]
dirs.sort(key=lambda p: p.stat().st_mtime, reverse=True)
if not dirs:
warn("No hay carpetas de backup 'docker-volumes-<timestamp>'.")
return
print("\nCarpetas de backup encontradas:")
for i, d in enumerate(dirs, 1):
print(f" {i}) {d.name}")
chosen = None
while not chosen:
sel = input("> Elige una carpeta (número) o Enter para salir: ").strip()
if not sel:
warn("Cancelado."); return
if sel.isdigit() and 1 <= int(sel) <= len(dirs):
chosen = dirs[int(sel)-1]
else:
print("Opción inválida.")
# Archivos en carpeta
archives = [p for p in chosen.iterdir() if p.is_file() and p.name.endswith(".tar.gz")]
archives.sort(key=lambda p: p.name)
if not archives:
warn("No hay .tar.gz en esa carpeta."); return
print("\nBackups disponibles:")
for i, f in enumerate(archives, 1):
base = parse_archive_basename(f.name) or f.name
print(f" {i}) {f.name} -> volumen: {base}")
print("\nOpciones de selección:")
print(" a) Restaurar TODOS")
print(" s) Elegir algunos (ej: 1,3,5)")
selected: List[pathlib.Path] = []
while not selected:
mode = input("> Elige 'a' o 's': ").strip().lower()
if mode == "a":
selected = archives
elif mode == "s":
picks = input("> Números separados por coma: ").strip()
try:
idxs = [int(x.strip())-1 for x in picks.split(",") if x.strip()]
selected = [archives[i] for i in sorted(set(i for i in idxs if 0 <= i < len(archives)))]
except Exception:
selected = []
else:
print("Opción inválida.")
# Modo de restauración
choice = pick_restore_mode()
if choice == "1":
restore_traditional(chosen, selected)
else:
restore_with_compose_labels(chosen, selected)
ok("\nProceso finalizado.")
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
print("\n✓ Interrumpido por el usuario (Ctrl+C).")
@@ -1,22 +1,20 @@
# Dockerfile.dev # Dockerfile.dev
FROM node:20.17 FROM node:22.18
# Definir variables de entorno con valores predeterminados # Definir variables de entorno con valores predeterminados
ARG NODE_ENV=development # ARG NODE_ENV=production
ARG PORT=3000 # ARG PORT=3000
RUN apt-get update
RUN apt-get install -y curl && rm -rf /var/lib/apt/lists/*
# Copia archivos de configuración primero para aprovechar el cache # Copia archivos de configuración primero para aprovechar el cache
COPY package*.json ./ COPY package*.json ./
# Instala dependencias # Instala dependencias
RUN npm i express pg dotenv cors RUN npm i
RUN npm i --save-dev nodemon
# Copia el resto de la app # Copia el resto de la app
COPY . . COPY . .
# Expone el puerto CMD ["npm", "run", "start"]
EXPOSE 3000
# Usa nodemon para hot reload si lo tenés
CMD ["npm", "run", "dev"]
+164 -21
View File
@@ -1,19 +1,21 @@
{ {
"name": "app", "name": "aplication",
"version": "1.0.0", "version": "1.0.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "app", "name": "aplication",
"version": "1.0.0", "version": "1.0.0",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"chalk": "^5.6.0",
"cors": "^2.8.5", "cors": "^2.8.5",
"dotenv": "^17.2.1", "dotenv": "^17.2.1",
"express": "^5.1.0", "express": "^5.1.0",
"express-ejs-layouts": "^2.5.1", "express-ejs-layouts": "^2.5.1",
"pg": "^8.16.3" "pg": "^8.16.3",
"pg-format": "^1.0.4"
}, },
"devDependencies": { "devDependencies": {
"cross-env": "^10.0.0", "cross-env": "^10.0.0",
@@ -24,12 +26,14 @@
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/@epic-web/invariant/-/invariant-1.0.0.tgz", "resolved": "https://registry.npmjs.org/@epic-web/invariant/-/invariant-1.0.0.tgz",
"integrity": "sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==", "integrity": "sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==",
"dev": true "dev": true,
"license": "MIT"
}, },
"node_modules/accepts": { "node_modules/accepts": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
"integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
"license": "MIT",
"dependencies": { "dependencies": {
"mime-types": "^3.0.0", "mime-types": "^3.0.0",
"negotiator": "^1.0.0" "negotiator": "^1.0.0"
@@ -43,6 +47,7 @@
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
"dev": true, "dev": true,
"license": "ISC",
"dependencies": { "dependencies": {
"normalize-path": "^3.0.0", "normalize-path": "^3.0.0",
"picomatch": "^2.0.4" "picomatch": "^2.0.4"
@@ -55,13 +60,15 @@
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true "dev": true,
"license": "MIT"
}, },
"node_modules/binary-extensions": { "node_modules/binary-extensions": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
"integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
"dev": true, "dev": true,
"license": "MIT",
"engines": { "engines": {
"node": ">=8" "node": ">=8"
}, },
@@ -73,6 +80,7 @@
"version": "2.2.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz",
"integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==",
"license": "MIT",
"dependencies": { "dependencies": {
"bytes": "^3.1.2", "bytes": "^3.1.2",
"content-type": "^1.0.5", "content-type": "^1.0.5",
@@ -93,6 +101,7 @@
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"balanced-match": "^1.0.0", "balanced-match": "^1.0.0",
"concat-map": "0.0.1" "concat-map": "0.0.1"
@@ -103,6 +112,7 @@
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"fill-range": "^7.1.1" "fill-range": "^7.1.1"
}, },
@@ -114,6 +124,7 @@
"version": "3.1.2", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
"license": "MIT",
"engines": { "engines": {
"node": ">= 0.8" "node": ">= 0.8"
} }
@@ -122,6 +133,7 @@
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"license": "MIT",
"dependencies": { "dependencies": {
"es-errors": "^1.3.0", "es-errors": "^1.3.0",
"function-bind": "^1.1.2" "function-bind": "^1.1.2"
@@ -134,6 +146,7 @@
"version": "1.0.4", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
"license": "MIT",
"dependencies": { "dependencies": {
"call-bind-apply-helpers": "^1.0.2", "call-bind-apply-helpers": "^1.0.2",
"get-intrinsic": "^1.3.0" "get-intrinsic": "^1.3.0"
@@ -145,11 +158,24 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/chalk": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.0.tgz",
"integrity": "sha512-46QrSQFyVSEyYAgQ22hQ+zDa60YHA4fBstHmtSApj1Y5vKtG27fWowW03jCk5KcbXEWPZUIR894aARCA/G1kfQ==",
"license": "MIT",
"engines": {
"node": "^12.17.0 || ^14.13 || >=16.0.0"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/chokidar": { "node_modules/chokidar": {
"version": "3.6.0", "version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"anymatch": "~3.1.2", "anymatch": "~3.1.2",
"braces": "~3.0.2", "braces": "~3.0.2",
@@ -173,12 +199,14 @@
"version": "0.0.1", "version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"dev": true "dev": true,
"license": "MIT"
}, },
"node_modules/content-disposition": { "node_modules/content-disposition": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz",
"integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==",
"license": "MIT",
"dependencies": { "dependencies": {
"safe-buffer": "5.2.1" "safe-buffer": "5.2.1"
}, },
@@ -190,6 +218,7 @@
"version": "1.0.5", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
"license": "MIT",
"engines": { "engines": {
"node": ">= 0.6" "node": ">= 0.6"
} }
@@ -198,6 +227,7 @@
"version": "0.7.2", "version": "0.7.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
"license": "MIT",
"engines": { "engines": {
"node": ">= 0.6" "node": ">= 0.6"
} }
@@ -206,6 +236,7 @@
"version": "1.2.2", "version": "1.2.2",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
"integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
"license": "MIT",
"engines": { "engines": {
"node": ">=6.6.0" "node": ">=6.6.0"
} }
@@ -214,6 +245,7 @@
"version": "2.8.5", "version": "2.8.5",
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
"integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
"license": "MIT",
"dependencies": { "dependencies": {
"object-assign": "^4", "object-assign": "^4",
"vary": "^1" "vary": "^1"
@@ -227,6 +259,7 @@
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-10.0.0.tgz", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-10.0.0.tgz",
"integrity": "sha512-aU8qlEK/nHYtVuN4p7UQgAwVljzMg8hB4YK5ThRqD2l/ziSnryncPNn7bMLt5cFYsKVKBh8HqLqyCoTupEUu7Q==", "integrity": "sha512-aU8qlEK/nHYtVuN4p7UQgAwVljzMg8hB4YK5ThRqD2l/ziSnryncPNn7bMLt5cFYsKVKBh8HqLqyCoTupEUu7Q==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"@epic-web/invariant": "^1.0.0", "@epic-web/invariant": "^1.0.0",
"cross-spawn": "^7.0.6" "cross-spawn": "^7.0.6"
@@ -244,6 +277,7 @@
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"path-key": "^3.1.0", "path-key": "^3.1.0",
"shebang-command": "^2.0.0", "shebang-command": "^2.0.0",
@@ -257,6 +291,7 @@
"version": "4.4.1", "version": "4.4.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
"integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
"license": "MIT",
"dependencies": { "dependencies": {
"ms": "^2.1.3" "ms": "^2.1.3"
}, },
@@ -273,6 +308,7 @@
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
"license": "MIT",
"engines": { "engines": {
"node": ">= 0.8" "node": ">= 0.8"
} }
@@ -281,6 +317,7 @@
"version": "17.2.1", "version": "17.2.1",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.1.tgz", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.1.tgz",
"integrity": "sha512-kQhDYKZecqnM0fCnzI5eIv5L4cAe/iRI+HqMbO/hbRdTAeXDG+M9FjipUxNfbARuEg4iHIbhnhs78BCHNbSxEQ==", "integrity": "sha512-kQhDYKZecqnM0fCnzI5eIv5L4cAe/iRI+HqMbO/hbRdTAeXDG+M9FjipUxNfbARuEg4iHIbhnhs78BCHNbSxEQ==",
"license": "BSD-2-Clause",
"engines": { "engines": {
"node": ">=12" "node": ">=12"
}, },
@@ -292,6 +329,7 @@
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"license": "MIT",
"dependencies": { "dependencies": {
"call-bind-apply-helpers": "^1.0.1", "call-bind-apply-helpers": "^1.0.1",
"es-errors": "^1.3.0", "es-errors": "^1.3.0",
@@ -304,12 +342,14 @@
"node_modules/ee-first": { "node_modules/ee-first": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
"license": "MIT"
}, },
"node_modules/encodeurl": { "node_modules/encodeurl": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
"license": "MIT",
"engines": { "engines": {
"node": ">= 0.8" "node": ">= 0.8"
} }
@@ -318,6 +358,7 @@
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"license": "MIT",
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
} }
@@ -326,6 +367,7 @@
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"license": "MIT",
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
} }
@@ -334,6 +376,7 @@
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
"license": "MIT",
"dependencies": { "dependencies": {
"es-errors": "^1.3.0" "es-errors": "^1.3.0"
}, },
@@ -344,12 +387,14 @@
"node_modules/escape-html": { "node_modules/escape-html": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
"license": "MIT"
}, },
"node_modules/etag": { "node_modules/etag": {
"version": "1.8.1", "version": "1.8.1",
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
"license": "MIT",
"engines": { "engines": {
"node": ">= 0.6" "node": ">= 0.6"
} }
@@ -358,6 +403,7 @@
"version": "5.1.0", "version": "5.1.0",
"resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz",
"integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==",
"license": "MIT",
"dependencies": { "dependencies": {
"accepts": "^2.0.0", "accepts": "^2.0.0",
"body-parser": "^2.2.0", "body-parser": "^2.2.0",
@@ -405,6 +451,7 @@
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"to-regex-range": "^5.0.1" "to-regex-range": "^5.0.1"
}, },
@@ -416,6 +463,7 @@
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz",
"integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==",
"license": "MIT",
"dependencies": { "dependencies": {
"debug": "^4.4.0", "debug": "^4.4.0",
"encodeurl": "^2.0.0", "encodeurl": "^2.0.0",
@@ -432,6 +480,7 @@
"version": "0.2.0", "version": "0.2.0",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
"integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
"license": "MIT",
"engines": { "engines": {
"node": ">= 0.6" "node": ">= 0.6"
} }
@@ -440,6 +489,7 @@
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
"integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
"license": "MIT",
"engines": { "engines": {
"node": ">= 0.8" "node": ">= 0.8"
} }
@@ -450,6 +500,7 @@
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true, "dev": true,
"hasInstallScript": true, "hasInstallScript": true,
"license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
"darwin" "darwin"
@@ -462,6 +513,7 @@
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"license": "MIT",
"funding": { "funding": {
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
@@ -470,6 +522,7 @@
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"license": "MIT",
"dependencies": { "dependencies": {
"call-bind-apply-helpers": "^1.0.2", "call-bind-apply-helpers": "^1.0.2",
"es-define-property": "^1.0.1", "es-define-property": "^1.0.1",
@@ -493,6 +546,7 @@
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"license": "MIT",
"dependencies": { "dependencies": {
"dunder-proto": "^1.0.1", "dunder-proto": "^1.0.1",
"es-object-atoms": "^1.0.0" "es-object-atoms": "^1.0.0"
@@ -506,6 +560,7 @@
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true, "dev": true,
"license": "ISC",
"dependencies": { "dependencies": {
"is-glob": "^4.0.1" "is-glob": "^4.0.1"
}, },
@@ -517,6 +572,7 @@
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"license": "MIT",
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
}, },
@@ -529,6 +585,7 @@
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
"dev": true, "dev": true,
"license": "MIT",
"engines": { "engines": {
"node": ">=4" "node": ">=4"
} }
@@ -537,6 +594,7 @@
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"license": "MIT",
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
}, },
@@ -548,6 +606,7 @@
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"license": "MIT",
"dependencies": { "dependencies": {
"function-bind": "^1.1.2" "function-bind": "^1.1.2"
}, },
@@ -559,6 +618,7 @@
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
"integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
"license": "MIT",
"dependencies": { "dependencies": {
"depd": "2.0.0", "depd": "2.0.0",
"inherits": "2.0.4", "inherits": "2.0.4",
@@ -574,6 +634,7 @@
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
"license": "MIT",
"engines": { "engines": {
"node": ">= 0.8" "node": ">= 0.8"
} }
@@ -582,6 +643,7 @@
"version": "0.6.3", "version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"license": "MIT",
"dependencies": { "dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0" "safer-buffer": ">= 2.1.2 < 3.0.0"
}, },
@@ -593,17 +655,20 @@
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
"integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==",
"dev": true "dev": true,
"license": "ISC"
}, },
"node_modules/inherits": { "node_modules/inherits": {
"version": "2.0.4", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"license": "ISC"
}, },
"node_modules/ipaddr.js": { "node_modules/ipaddr.js": {
"version": "1.9.1", "version": "1.9.1",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
"license": "MIT",
"engines": { "engines": {
"node": ">= 0.10" "node": ">= 0.10"
} }
@@ -613,6 +678,7 @@
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"binary-extensions": "^2.0.0" "binary-extensions": "^2.0.0"
}, },
@@ -625,6 +691,7 @@
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
"dev": true, "dev": true,
"license": "MIT",
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@@ -634,6 +701,7 @@
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"is-extglob": "^2.1.1" "is-extglob": "^2.1.1"
}, },
@@ -646,6 +714,7 @@
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true, "dev": true,
"license": "MIT",
"engines": { "engines": {
"node": ">=0.12.0" "node": ">=0.12.0"
} }
@@ -653,18 +722,21 @@
"node_modules/is-promise": { "node_modules/is-promise": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
"integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==" "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
"license": "MIT"
}, },
"node_modules/isexe": { "node_modules/isexe": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"dev": true "dev": true,
"license": "ISC"
}, },
"node_modules/math-intrinsics": { "node_modules/math-intrinsics": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
"license": "MIT",
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
} }
@@ -673,6 +745,7 @@
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
"integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
"license": "MIT",
"engines": { "engines": {
"node": ">= 0.8" "node": ">= 0.8"
} }
@@ -681,6 +754,7 @@
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
"integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
"license": "MIT",
"engines": { "engines": {
"node": ">=18" "node": ">=18"
}, },
@@ -692,6 +766,7 @@
"version": "1.54.0", "version": "1.54.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
"integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
"license": "MIT",
"engines": { "engines": {
"node": ">= 0.6" "node": ">= 0.6"
} }
@@ -700,6 +775,7 @@
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz",
"integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==",
"license": "MIT",
"dependencies": { "dependencies": {
"mime-db": "^1.54.0" "mime-db": "^1.54.0"
}, },
@@ -712,6 +788,7 @@
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true, "dev": true,
"license": "ISC",
"dependencies": { "dependencies": {
"brace-expansion": "^1.1.7" "brace-expansion": "^1.1.7"
}, },
@@ -722,12 +799,14 @@
"node_modules/ms": { "node_modules/ms": {
"version": "2.1.3", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
}, },
"node_modules/negotiator": { "node_modules/negotiator": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
"integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
"license": "MIT",
"engines": { "engines": {
"node": ">= 0.6" "node": ">= 0.6"
} }
@@ -737,6 +816,7 @@
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz",
"integrity": "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==", "integrity": "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"chokidar": "^3.5.2", "chokidar": "^3.5.2",
"debug": "^4", "debug": "^4",
@@ -765,6 +845,7 @@
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
"dev": true, "dev": true,
"license": "MIT",
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@@ -773,6 +854,7 @@
"version": "4.1.1", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
"license": "MIT",
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@@ -781,6 +863,7 @@
"version": "1.13.4", "version": "1.13.4",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
"integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
"license": "MIT",
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
}, },
@@ -792,6 +875,7 @@
"version": "2.4.1", "version": "2.4.1",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
"integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
"license": "MIT",
"dependencies": { "dependencies": {
"ee-first": "1.1.1" "ee-first": "1.1.1"
}, },
@@ -803,6 +887,7 @@
"version": "1.4.0", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"license": "ISC",
"dependencies": { "dependencies": {
"wrappy": "1" "wrappy": "1"
} }
@@ -811,6 +896,7 @@
"version": "1.3.3", "version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
"license": "MIT",
"engines": { "engines": {
"node": ">= 0.8" "node": ">= 0.8"
} }
@@ -820,6 +906,7 @@
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"dev": true, "dev": true,
"license": "MIT",
"engines": { "engines": {
"node": ">=8" "node": ">=8"
} }
@@ -828,6 +915,7 @@
"version": "8.2.0", "version": "8.2.0",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz",
"integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==",
"license": "MIT",
"engines": { "engines": {
"node": ">=16" "node": ">=16"
} }
@@ -836,6 +924,7 @@
"version": "8.16.3", "version": "8.16.3",
"resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz", "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz",
"integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==", "integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==",
"license": "MIT",
"dependencies": { "dependencies": {
"pg-connection-string": "^2.9.1", "pg-connection-string": "^2.9.1",
"pg-pool": "^3.10.1", "pg-pool": "^3.10.1",
@@ -862,17 +951,29 @@
"version": "1.2.7", "version": "1.2.7",
"resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.2.7.tgz", "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.2.7.tgz",
"integrity": "sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==", "integrity": "sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==",
"license": "MIT",
"optional": true "optional": true
}, },
"node_modules/pg-connection-string": { "node_modules/pg-connection-string": {
"version": "2.9.1", "version": "2.9.1",
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.9.1.tgz", "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.9.1.tgz",
"integrity": "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==" "integrity": "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==",
"license": "MIT"
},
"node_modules/pg-format": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/pg-format/-/pg-format-1.0.4.tgz",
"integrity": "sha512-YyKEF78pEA6wwTAqOUaHIN/rWpfzzIuMh9KdAhc3rSLQ/7zkRFcCgYBAEGatDstLyZw4g0s9SNICmaTGnBVeyw==",
"license": "MIT",
"engines": {
"node": ">=4.0"
}
}, },
"node_modules/pg-int8": { "node_modules/pg-int8": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
"integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==",
"license": "ISC",
"engines": { "engines": {
"node": ">=4.0.0" "node": ">=4.0.0"
} }
@@ -881,6 +982,7 @@
"version": "3.10.1", "version": "3.10.1",
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.10.1.tgz", "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.10.1.tgz",
"integrity": "sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg==", "integrity": "sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg==",
"license": "MIT",
"peerDependencies": { "peerDependencies": {
"pg": ">=8.0" "pg": ">=8.0"
} }
@@ -888,12 +990,14 @@
"node_modules/pg-protocol": { "node_modules/pg-protocol": {
"version": "1.10.3", "version": "1.10.3",
"resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.10.3.tgz", "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.10.3.tgz",
"integrity": "sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ==" "integrity": "sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ==",
"license": "MIT"
}, },
"node_modules/pg-types": { "node_modules/pg-types": {
"version": "2.2.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
"integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
"license": "MIT",
"dependencies": { "dependencies": {
"pg-int8": "1.0.1", "pg-int8": "1.0.1",
"postgres-array": "~2.0.0", "postgres-array": "~2.0.0",
@@ -909,6 +1013,7 @@
"version": "1.0.5", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz",
"integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==",
"license": "MIT",
"dependencies": { "dependencies": {
"split2": "^4.1.0" "split2": "^4.1.0"
} }
@@ -918,6 +1023,7 @@
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"dev": true, "dev": true,
"license": "MIT",
"engines": { "engines": {
"node": ">=8.6" "node": ">=8.6"
}, },
@@ -929,6 +1035,7 @@
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
"integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==",
"license": "MIT",
"engines": { "engines": {
"node": ">=4" "node": ">=4"
} }
@@ -937,6 +1044,7 @@
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz",
"integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==",
"license": "MIT",
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@@ -945,6 +1053,7 @@
"version": "1.0.7", "version": "1.0.7",
"resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
"integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==",
"license": "MIT",
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@@ -953,6 +1062,7 @@
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
"integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
"license": "MIT",
"dependencies": { "dependencies": {
"xtend": "^4.0.0" "xtend": "^4.0.0"
}, },
@@ -964,6 +1074,7 @@
"version": "2.0.7", "version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
"integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
"license": "MIT",
"dependencies": { "dependencies": {
"forwarded": "0.2.0", "forwarded": "0.2.0",
"ipaddr.js": "1.9.1" "ipaddr.js": "1.9.1"
@@ -976,12 +1087,14 @@
"version": "1.1.8", "version": "1.1.8",
"resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
"integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==",
"dev": true "dev": true,
"license": "MIT"
}, },
"node_modules/qs": { "node_modules/qs": {
"version": "6.14.0", "version": "6.14.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
"integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
"license": "BSD-3-Clause",
"dependencies": { "dependencies": {
"side-channel": "^1.1.0" "side-channel": "^1.1.0"
}, },
@@ -996,6 +1109,7 @@
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
"license": "MIT",
"engines": { "engines": {
"node": ">= 0.6" "node": ">= 0.6"
} }
@@ -1004,6 +1118,7 @@
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz",
"integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==",
"license": "MIT",
"dependencies": { "dependencies": {
"bytes": "3.1.2", "bytes": "3.1.2",
"http-errors": "2.0.0", "http-errors": "2.0.0",
@@ -1019,6 +1134,7 @@
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"picomatch": "^2.2.1" "picomatch": "^2.2.1"
}, },
@@ -1030,6 +1146,7 @@
"version": "2.2.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
"integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
"license": "MIT",
"dependencies": { "dependencies": {
"debug": "^4.4.0", "debug": "^4.4.0",
"depd": "^2.0.0", "depd": "^2.0.0",
@@ -1058,18 +1175,21 @@
"type": "consulting", "type": "consulting",
"url": "https://feross.org/support" "url": "https://feross.org/support"
} }
] ],
"license": "MIT"
}, },
"node_modules/safer-buffer": { "node_modules/safer-buffer": {
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"license": "MIT"
}, },
"node_modules/semver": { "node_modules/semver": {
"version": "7.7.2", "version": "7.7.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
"integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
"dev": true, "dev": true,
"license": "ISC",
"bin": { "bin": {
"semver": "bin/semver.js" "semver": "bin/semver.js"
}, },
@@ -1081,6 +1201,7 @@
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz",
"integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==",
"license": "MIT",
"dependencies": { "dependencies": {
"debug": "^4.3.5", "debug": "^4.3.5",
"encodeurl": "^2.0.0", "encodeurl": "^2.0.0",
@@ -1102,6 +1223,7 @@
"version": "2.2.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz",
"integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==",
"license": "MIT",
"dependencies": { "dependencies": {
"encodeurl": "^2.0.0", "encodeurl": "^2.0.0",
"escape-html": "^1.0.3", "escape-html": "^1.0.3",
@@ -1115,13 +1237,15 @@
"node_modules/setprototypeof": { "node_modules/setprototypeof": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
"license": "ISC"
}, },
"node_modules/shebang-command": { "node_modules/shebang-command": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"shebang-regex": "^3.0.0" "shebang-regex": "^3.0.0"
}, },
@@ -1134,6 +1258,7 @@
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"dev": true, "dev": true,
"license": "MIT",
"engines": { "engines": {
"node": ">=8" "node": ">=8"
} }
@@ -1142,6 +1267,7 @@
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
"integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
"license": "MIT",
"dependencies": { "dependencies": {
"es-errors": "^1.3.0", "es-errors": "^1.3.0",
"object-inspect": "^1.13.3", "object-inspect": "^1.13.3",
@@ -1160,6 +1286,7 @@
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
"integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
"license": "MIT",
"dependencies": { "dependencies": {
"es-errors": "^1.3.0", "es-errors": "^1.3.0",
"object-inspect": "^1.13.3" "object-inspect": "^1.13.3"
@@ -1175,6 +1302,7 @@
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
"integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
"license": "MIT",
"dependencies": { "dependencies": {
"call-bound": "^1.0.2", "call-bound": "^1.0.2",
"es-errors": "^1.3.0", "es-errors": "^1.3.0",
@@ -1192,6 +1320,7 @@
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
"integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
"license": "MIT",
"dependencies": { "dependencies": {
"call-bound": "^1.0.2", "call-bound": "^1.0.2",
"es-errors": "^1.3.0", "es-errors": "^1.3.0",
@@ -1211,6 +1340,7 @@
"resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
"integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"semver": "^7.5.3" "semver": "^7.5.3"
}, },
@@ -1222,6 +1352,7 @@
"version": "4.2.0", "version": "4.2.0",
"resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
"integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
"license": "ISC",
"engines": { "engines": {
"node": ">= 10.x" "node": ">= 10.x"
} }
@@ -1230,6 +1361,7 @@
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
"integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
"license": "MIT",
"engines": { "engines": {
"node": ">= 0.8" "node": ">= 0.8"
} }
@@ -1239,6 +1371,7 @@
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"has-flag": "^3.0.0" "has-flag": "^3.0.0"
}, },
@@ -1251,6 +1384,7 @@
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"is-number": "^7.0.0" "is-number": "^7.0.0"
}, },
@@ -1262,6 +1396,7 @@
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
"license": "MIT",
"engines": { "engines": {
"node": ">=0.6" "node": ">=0.6"
} }
@@ -1271,6 +1406,7 @@
"resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz",
"integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==",
"dev": true, "dev": true,
"license": "ISC",
"bin": { "bin": {
"nodetouch": "bin/nodetouch.js" "nodetouch": "bin/nodetouch.js"
} }
@@ -1279,6 +1415,7 @@
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
"integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==",
"license": "MIT",
"dependencies": { "dependencies": {
"content-type": "^1.0.5", "content-type": "^1.0.5",
"media-typer": "^1.1.0", "media-typer": "^1.1.0",
@@ -1292,12 +1429,14 @@
"version": "2.0.5", "version": "2.0.5",
"resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
"integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==",
"dev": true "dev": true,
"license": "MIT"
}, },
"node_modules/unpipe": { "node_modules/unpipe": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
"license": "MIT",
"engines": { "engines": {
"node": ">= 0.8" "node": ">= 0.8"
} }
@@ -1306,6 +1445,7 @@
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
"license": "MIT",
"engines": { "engines": {
"node": ">= 0.8" "node": ">= 0.8"
} }
@@ -1315,6 +1455,7 @@
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"dev": true, "dev": true,
"license": "ISC",
"dependencies": { "dependencies": {
"isexe": "^2.0.0" "isexe": "^2.0.0"
}, },
@@ -1328,12 +1469,14 @@
"node_modules/wrappy": { "node_modules/wrappy": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"license": "ISC"
}, },
"node_modules/xtend": { "node_modules/xtend": {
"version": "4.0.2", "version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
"license": "MIT",
"engines": { "engines": {
"node": ">=0.4" "node": ">=0.4"
} }
+6 -4
View File
@@ -1,10 +1,10 @@
{ {
"name": "app", "name": "aplication",
"version": "1.0.0", "version": "1.0.0",
"main": "index.js", "main": "src/index.js",
"scripts": { "scripts": {
"start": "NODE_ENV=production node ./src/index.js", "start": "NODE_ENV=production node ./src/index.js",
"dev": "NODE_ENV=development node ./src/index.js", "dev": "NODE_ENV=development npx nodemon ./src/index.js",
"test": "NODE_ENV=stage node ./src/index.js" "test": "NODE_ENV=stage node ./src/index.js"
}, },
"author": "Mateo Saldain", "author": "Mateo Saldain",
@@ -15,11 +15,13 @@
"nodemon": "^3.1.10" "nodemon": "^3.1.10"
}, },
"dependencies": { "dependencies": {
"chalk": "^5.6.0",
"cors": "^2.8.5", "cors": "^2.8.5",
"dotenv": "^17.2.1", "dotenv": "^17.2.1",
"express": "^5.1.0", "express": "^5.1.0",
"express-ejs-layouts": "^2.5.1", "express-ejs-layouts": "^2.5.1",
"pg": "^8.16.3" "pg": "^8.16.3",
"pg-format": "^1.0.4"
}, },
"keywords": [], "keywords": [],
"description": "" "description": ""
+23 -13
View File
@@ -1,4 +1,5 @@
// app/src/index.js // app/src/index.js
import chalk from 'chalk'; // Colores!
import express from 'express'; import express from 'express';
import expressLayouts from 'express-ejs-layouts'; import expressLayouts from 'express-ejs-layouts';
import cors from 'cors'; import cors from 'cors';
@@ -17,13 +18,13 @@ import dotenv, { config } from 'dotenv';
try { try {
if (process.env.NODE_ENV === 'development') { if (process.env.NODE_ENV === 'development') {
dotenv.config({ path: path.resolve(__dirname, '../.env.development' )}); dotenv.config({ path: path.resolve(__dirname, '../.env.development' )});
console.log("Activando entorno de -> development"); console.log(`Activando entorno de ->${chalk.green(` DEVELOPMENT `)}`);
} else if (process.env.NODE_ENV === 'stage') { } else if (process.env.NODE_ENV === 'stage') {
dotenv.config({ path: path.resolve(__dirname, '../.env.test' )}); dotenv.config({ path: path.resolve(__dirname, '../.env.test' )});
console.log("Activando entorno de -> testing"); console.log(`Activando entorno de ->${chalk.yellow(` TESTING `)}`);
} else if (process.env.NODE_ENV === 'production') { } else if (process.env.NODE_ENV === 'production') {
dotenv.config({ path: path.resolve(__dirname, '../.env' )}); dotenv.config({ path: path.resolve(__dirname, '../.env.production' )});
console.log("Activando entorno de -> producción"); console.log(`Activando entorno de ->${chalk.red(` PRODUCTION `)}`);
} }
} catch (error) { } catch (error) {
console.log("A ocurrido un error al seleccionar el entorno. \nError: " + error); console.log("A ocurrido un error al seleccionar el entorno. \nError: " + error);
@@ -33,6 +34,7 @@ try {
const app = express(); const app = express();
app.use(cors()); app.use(cors());
app.use(express.json()); app.use(express.json());
app.use(express.static(path.join(__dirname, 'pages')));
// Configuración de conexión PostgreSQL // Configuración de conexión PostgreSQL
@@ -51,22 +53,19 @@ async function verificarConexion() {
try { try {
const client = await pool.connect(); const client = await pool.connect();
const res = await client.query('SELECT NOW() AS hora'); const res = await client.query('SELECT NOW() AS hora');
console.log('Conexión con la base de datos fue exitosa.'); console.log(`\nConexión con la base de datos ${chalk.green(process.env.DB_NAME)} fue exitosa.`);
console.log('Fecha y hora actual de la base de datos:', res.rows[0].hora); console.log('Fecha y hora actual de la base de datos:', res.rows[0].hora);
client.release(); // liberar el cliente de nuevo al pool client.release(); // liberar el cliente de nuevo al pool
} catch (error) { } catch (error) {
console.error('Error al conectar con la base de datos al iniciar:', error.message); console.error('Error al conectar con la base de datos al iniciar:', error.message);
} console.error(`Troubleshooting:\n1. Compruebe que las bases de datos se iniciaron correctamente.\n2. Verifique las credenciales y puertos de acceso a la base de datos.\n3. Si está conectandose a una base de datos externa a localhost, verifique las reglas del firewal de entrada y salida de ambos dispositivos.`);
}
}
}
// === Servir páginas estáticas === // === Servir páginas estáticas ===
app.use('/pages', express.static(path.join(__dirname, 'pages')));
// Rutas de conveniencia para abrir cada página rápido:
// (Opcional: puedes usar directamente /pages/roles.html, etc.)
app.get('/roles', (req, res) => res.sendFile(path.join(__dirname, 'pages', 'roles.html'))); app.get('/roles', (req, res) => res.sendFile(path.join(__dirname, 'pages', 'roles.html')));
app.get('/usuarios', (req, res) => res.sendFile(path.join(__dirname, 'pages', 'usuarios.html'))); app.get('/usuarios', (req, res) => res.sendFile(path.join(__dirname, 'pages', 'usuarios.html')));
app.get('/categorias',(req, res) => res.sendFile(path.join(__dirname, 'pages', 'categorias.html'))); app.get('/categorias',(req, res) => res.sendFile(path.join(__dirname, 'pages', 'categorias.html')));
@@ -231,10 +230,21 @@ async function q(text, params) {
}); });
// Colores personalizados
let primaryColor = chalk.hex('#'+`${process.env.COL_PRI}`);
let secondaryColor = chalk.hex('#'+`${process.env.COL_SEC}`);
// let backgroundColor = chalk.hex('#'+`${process.env.COL_BG}`);
app.use(expressLayouts); app.use(expressLayouts);
// Iniciar servidor // Iniciar servidor
app.listen( process.env.PORT, () => { app.listen( process.env.PORT, () => {
console.log(`Servidor corriendo en http://localhost:${process.env.PORT}`); console.log(`Servidor de ${chalk.red('aplicación')} de ${secondaryColor('SuiteCoffee')} corriendo en ${chalk.yellow(`http://localhost:${process.env.PORT}\n`)}` );
console.log('Estableciendo conexión con la db...'); console.log(chalk.grey(`Comprobando accesibilidad a la db ${chalk.green(process.env.DB_NAME)} del host ${chalk.white(`${process.env.DB_HOST}`)} ...`));
verificarConexion(); verificarConexion();
}); });
app.get("/health", async (req, res) => {
// Podés chequear DB aquí. 200 = healthy; 503 = not ready.
res.status(200).json({ status: "ok" });
});
@@ -1,22 +1,20 @@
# Dockerfile.dev # Dockerfile.dev
FROM node:20.17 FROM node:22.18
# Definir variables de entorno con valores predeterminados # Definir variables de entorno con valores predeterminados
ARG NODE_ENV=development # ARG NODE_ENV=production
ARG PORT=3000 # ARG PORT=4000
RUN apt-get update
RUN apt-get install -y curl && rm -rf /var/lib/apt/lists/*
# Copia archivos de configuración primero para aprovechar el cache # Copia archivos de configuración primero para aprovechar el cache
COPY package*.json ./ COPY package*.json ./
# Instala dependencias # Instala dependencias
RUN npm i express pg dotenv cors RUN npm i
RUN npm i --save-dev nodemon
# Copia el resto de la app # Copia el resto de la app
COPY . . COPY . .
# Expone el puerto CMD ["npm", "run", "start"]
EXPOSE 3000
# Usa nodemon para hot reload si lo tenés
CMD ["npm", "run", "dev"]
+534 -8
View File
@@ -1,19 +1,22 @@
{ {
"name": "auth", "name": "authentication",
"version": "1.0.0", "version": "1.0.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "auth", "name": "authentication",
"version": "1.0.0", "version": "1.0.0",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"bcrypt": "^5.1.1",
"chalk": "^5.6.0",
"cors": "^2.8.5", "cors": "^2.8.5",
"dotenv": "^17.2.1", "dotenv": "^17.2.1",
"express": "^5.1.0", "express": "^5.1.0",
"express-ejs-layouts": "^2.5.1", "express-ejs-layouts": "^2.5.1",
"pg": "^8.16.3" "pg": "^8.16.3",
"pg-format": "^1.0.4"
}, },
"devDependencies": { "devDependencies": {
"cross-env": "^10.0.0", "cross-env": "^10.0.0",
@@ -27,6 +30,32 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@mapbox/node-pre-gyp": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz",
"integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==",
"license": "BSD-3-Clause",
"dependencies": {
"detect-libc": "^2.0.0",
"https-proxy-agent": "^5.0.0",
"make-dir": "^3.1.0",
"node-fetch": "^2.6.7",
"nopt": "^5.0.0",
"npmlog": "^5.0.1",
"rimraf": "^3.0.2",
"semver": "^7.3.5",
"tar": "^6.1.11"
},
"bin": {
"node-pre-gyp": "bin/node-pre-gyp"
}
},
"node_modules/abbrev": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
"license": "ISC"
},
"node_modules/accepts": { "node_modules/accepts": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
@@ -40,6 +69,27 @@
"node": ">= 0.6" "node": ">= 0.6"
} }
}, },
"node_modules/agent-base": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
"license": "MIT",
"dependencies": {
"debug": "4"
},
"engines": {
"node": ">= 6.0.0"
}
},
"node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/anymatch": { "node_modules/anymatch": {
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
@@ -54,13 +104,46 @@
"node": ">= 8" "node": ">= 8"
} }
}, },
"node_modules/aproba": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/aproba/-/aproba-2.1.0.tgz",
"integrity": "sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==",
"license": "ISC"
},
"node_modules/are-we-there-yet": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz",
"integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==",
"deprecated": "This package is no longer supported.",
"license": "ISC",
"dependencies": {
"delegates": "^1.0.0",
"readable-stream": "^3.6.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/balanced-match": { "node_modules/balanced-match": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/bcrypt": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz",
"integrity": "sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"@mapbox/node-pre-gyp": "^1.0.11",
"node-addon-api": "^5.0.0"
},
"engines": {
"node": ">= 10.0.0"
}
},
"node_modules/binary-extensions": { "node_modules/binary-extensions": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
@@ -98,7 +181,6 @@
"version": "1.1.12", "version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"balanced-match": "^1.0.0", "balanced-match": "^1.0.0",
@@ -156,6 +238,18 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/chalk": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.0.tgz",
"integrity": "sha512-46QrSQFyVSEyYAgQ22hQ+zDa60YHA4fBstHmtSApj1Y5vKtG27fWowW03jCk5KcbXEWPZUIR894aARCA/G1kfQ==",
"license": "MIT",
"engines": {
"node": "^12.17.0 || ^14.13 || >=16.0.0"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/chokidar": { "node_modules/chokidar": {
"version": "3.6.0", "version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
@@ -181,13 +275,36 @@
"fsevents": "~2.3.2" "fsevents": "~2.3.2"
} }
}, },
"node_modules/chownr": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
"integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==",
"license": "ISC",
"engines": {
"node": ">=10"
}
},
"node_modules/color-support": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz",
"integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==",
"license": "ISC",
"bin": {
"color-support": "bin.js"
}
},
"node_modules/concat-map": { "node_modules/concat-map": {
"version": "0.0.1", "version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/console-control-strings": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
"integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==",
"license": "ISC"
},
"node_modules/content-disposition": { "node_modules/content-disposition": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz",
@@ -290,6 +407,12 @@
} }
} }
}, },
"node_modules/delegates": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
"integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==",
"license": "MIT"
},
"node_modules/depd": { "node_modules/depd": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
@@ -299,6 +422,15 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/detect-libc": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz",
"integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==",
"license": "Apache-2.0",
"engines": {
"node": ">=8"
}
},
"node_modules/dotenv": { "node_modules/dotenv": {
"version": "17.2.1", "version": "17.2.1",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.1.tgz", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.1.tgz",
@@ -331,6 +463,12 @@
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"license": "MIT"
},
"node_modules/encodeurl": { "node_modules/encodeurl": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
@@ -480,6 +618,36 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/fs-minipass": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
"integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==",
"license": "ISC",
"dependencies": {
"minipass": "^3.0.0"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/fs-minipass/node_modules/minipass": {
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
"integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
"license": "ISC",
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
"license": "ISC"
},
"node_modules/fsevents": { "node_modules/fsevents": {
"version": "2.3.3", "version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
@@ -504,6 +672,27 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/gauge": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz",
"integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==",
"deprecated": "This package is no longer supported.",
"license": "ISC",
"dependencies": {
"aproba": "^1.0.3 || ^2.0.0",
"color-support": "^1.1.2",
"console-control-strings": "^1.0.0",
"has-unicode": "^2.0.1",
"object-assign": "^4.1.1",
"signal-exit": "^3.0.0",
"string-width": "^4.2.3",
"strip-ansi": "^6.0.1",
"wide-align": "^1.1.2"
},
"engines": {
"node": ">=10"
}
},
"node_modules/get-intrinsic": { "node_modules/get-intrinsic": {
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
@@ -541,6 +730,27 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"deprecated": "Glob versions prior to v9 are no longer supported",
"license": "ISC",
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.1.1",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
},
"engines": {
"node": "*"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/glob-parent": { "node_modules/glob-parent": {
"version": "5.1.2", "version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
@@ -588,6 +798,12 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/has-unicode": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
"integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==",
"license": "ISC"
},
"node_modules/hasown": { "node_modules/hasown": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
@@ -625,6 +841,19 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/https-proxy-agent": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
"integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
"license": "MIT",
"dependencies": {
"agent-base": "6",
"debug": "4"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/iconv-lite": { "node_modules/iconv-lite": {
"version": "0.6.3", "version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
@@ -644,6 +873,17 @@
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
"deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
"license": "ISC",
"dependencies": {
"once": "^1.3.0",
"wrappy": "1"
}
},
"node_modules/inherits": { "node_modules/inherits": {
"version": "2.0.4", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
@@ -682,6 +922,15 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/is-glob": { "node_modules/is-glob": {
"version": "4.0.3", "version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
@@ -718,6 +967,30 @@
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/make-dir": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
"integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
"license": "MIT",
"dependencies": {
"semver": "^6.0.0"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/make-dir/node_modules/semver": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
}
},
"node_modules/math-intrinsics": { "node_modules/math-intrinsics": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@@ -773,7 +1046,6 @@
"version": "3.1.2", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true,
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"brace-expansion": "^1.1.7" "brace-expansion": "^1.1.7"
@@ -782,6 +1054,52 @@
"node": "*" "node": "*"
} }
}, },
"node_modules/minipass": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz",
"integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==",
"license": "ISC",
"engines": {
"node": ">=8"
}
},
"node_modules/minizlib": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz",
"integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==",
"license": "MIT",
"dependencies": {
"minipass": "^3.0.0",
"yallist": "^4.0.0"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/minizlib/node_modules/minipass": {
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
"integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
"license": "ISC",
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/mkdirp": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
"license": "MIT",
"bin": {
"mkdirp": "bin/cmd.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/ms": { "node_modules/ms": {
"version": "2.1.3", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -797,6 +1115,32 @@
"node": ">= 0.6" "node": ">= 0.6"
} }
}, },
"node_modules/node-addon-api": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz",
"integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==",
"license": "MIT"
},
"node_modules/node-fetch": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
"license": "MIT",
"dependencies": {
"whatwg-url": "^5.0.0"
},
"engines": {
"node": "4.x || >=6.0.0"
},
"peerDependencies": {
"encoding": "^0.1.0"
},
"peerDependenciesMeta": {
"encoding": {
"optional": true
}
}
},
"node_modules/nodemon": { "node_modules/nodemon": {
"version": "3.1.10", "version": "3.1.10",
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz",
@@ -826,6 +1170,21 @@
"url": "https://opencollective.com/nodemon" "url": "https://opencollective.com/nodemon"
} }
}, },
"node_modules/nopt": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz",
"integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==",
"license": "ISC",
"dependencies": {
"abbrev": "1"
},
"bin": {
"nopt": "bin/nopt.js"
},
"engines": {
"node": ">=6"
}
},
"node_modules/normalize-path": { "node_modules/normalize-path": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
@@ -836,6 +1195,19 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/npmlog": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz",
"integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==",
"deprecated": "This package is no longer supported.",
"license": "ISC",
"dependencies": {
"are-we-there-yet": "^2.0.0",
"console-control-strings": "^1.1.0",
"gauge": "^3.0.0",
"set-blocking": "^2.0.0"
}
},
"node_modules/object-assign": { "node_modules/object-assign": {
"version": "4.1.1", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@@ -887,6 +1259,15 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/path-key": { "node_modules/path-key": {
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
@@ -946,6 +1327,15 @@
"integrity": "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==", "integrity": "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/pg-format": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/pg-format/-/pg-format-1.0.4.tgz",
"integrity": "sha512-YyKEF78pEA6wwTAqOUaHIN/rWpfzzIuMh9KdAhc3rSLQ/7zkRFcCgYBAEGatDstLyZw4g0s9SNICmaTGnBVeyw==",
"license": "MIT",
"engines": {
"node": ">=4.0"
}
},
"node_modules/pg-int8": { "node_modules/pg-int8": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
@@ -1106,6 +1496,20 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/readable-stream": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
"license": "MIT",
"dependencies": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/readdirp": { "node_modules/readdirp": {
"version": "3.6.0", "version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
@@ -1119,6 +1523,22 @@
"node": ">=8.10.0" "node": ">=8.10.0"
} }
}, },
"node_modules/rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
"deprecated": "Rimraf versions prior to v4 are no longer supported",
"license": "ISC",
"dependencies": {
"glob": "^7.1.3"
},
"bin": {
"rimraf": "bin.js"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/router": { "node_modules/router": {
"version": "2.2.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
@@ -1165,7 +1585,6 @@
"version": "7.7.2", "version": "7.7.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
"integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
"dev": true,
"license": "ISC", "license": "ISC",
"bin": { "bin": {
"semver": "bin/semver.js" "semver": "bin/semver.js"
@@ -1211,6 +1630,12 @@
"node": ">= 18" "node": ">= 18"
} }
}, },
"node_modules/set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
"license": "ISC"
},
"node_modules/setprototypeof": { "node_modules/setprototypeof": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
@@ -1312,6 +1737,12 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/signal-exit": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
"license": "ISC"
},
"node_modules/simple-update-notifier": { "node_modules/simple-update-notifier": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
@@ -1343,6 +1774,41 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
"license": "MIT",
"dependencies": {
"safe-buffer": "~5.2.0"
}
},
"node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/supports-color": { "node_modules/supports-color": {
"version": "5.5.0", "version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
@@ -1356,6 +1822,23 @@
"node": ">=4" "node": ">=4"
} }
}, },
"node_modules/tar": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz",
"integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==",
"license": "ISC",
"dependencies": {
"chownr": "^2.0.0",
"fs-minipass": "^2.0.0",
"minipass": "^5.0.0",
"minizlib": "^2.1.1",
"mkdirp": "^1.0.3",
"yallist": "^4.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/to-regex-range": { "node_modules/to-regex-range": {
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@@ -1388,6 +1871,12 @@
"nodetouch": "bin/nodetouch.js" "nodetouch": "bin/nodetouch.js"
} }
}, },
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
"license": "MIT"
},
"node_modules/type-is": { "node_modules/type-is": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
@@ -1418,6 +1907,12 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"license": "MIT"
},
"node_modules/vary": { "node_modules/vary": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
@@ -1427,6 +1922,22 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
"license": "BSD-2-Clause"
},
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"license": "MIT",
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
},
"node_modules/which": { "node_modules/which": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -1443,6 +1954,15 @@
"node": ">= 8" "node": ">= 8"
} }
}, },
"node_modules/wide-align": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz",
"integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==",
"license": "ISC",
"dependencies": {
"string-width": "^1.0.2 || 2 || 3 || 4"
}
},
"node_modules/wrappy": { "node_modules/wrappy": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
@@ -1457,6 +1977,12 @@
"engines": { "engines": {
"node": ">=0.4" "node": ">=0.4"
} }
},
"node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"license": "ISC"
} }
} }
} }
+7 -4
View File
@@ -1,10 +1,10 @@
{ {
"name": "auth", "name": "authentication",
"version": "1.0.0", "version": "1.0.0",
"main": "index.js", "main": "src/index.js",
"scripts": { "scripts": {
"start": "NODE_ENV=production node ./src/index.js", "start": "NODE_ENV=production node ./src/index.js",
"dev": "NODE_ENV=development node ./src/index.js", "dev": "NODE_ENV=development npx nodemon ./src/index.js",
"test": "NODE_ENV=stage node ./src/index.js" "test": "NODE_ENV=stage node ./src/index.js"
}, },
"author": "Mateo Saldain", "author": "Mateo Saldain",
@@ -15,11 +15,14 @@
"nodemon": "^3.1.10" "nodemon": "^3.1.10"
}, },
"dependencies": { "dependencies": {
"bcrypt": "^5.1.1",
"chalk": "^5.6.0",
"cors": "^2.8.5", "cors": "^2.8.5",
"dotenv": "^17.2.1", "dotenv": "^17.2.1",
"express": "^5.1.0", "express": "^5.1.0",
"express-ejs-layouts": "^2.5.1", "express-ejs-layouts": "^2.5.1",
"pg": "^8.16.3" "pg": "^8.16.3",
"pg-format": "^1.0.4"
}, },
"keywords": [], "keywords": [],
"description": "" "description": ""
+133 -13
View File
@@ -1,8 +1,10 @@
// auth/src/index.js // auth/src/index.js
import chalk from 'chalk';
import express from 'express'; import express from 'express';
import expressLayouts from 'express-ejs-layouts'; import expressLayouts from 'express-ejs-layouts';
import cors from 'cors'; import cors from 'cors';
import { Pool } from 'pg'; import { Pool } from 'pg';
import bcrypt from'bcrypt';
// Rutas // Rutas
import path from 'path'; import path from 'path';
@@ -17,22 +19,25 @@ import dotenv, { config } from 'dotenv';
try { try {
if (process.env.NODE_ENV === 'development') { if (process.env.NODE_ENV === 'development') {
dotenv.config({ path: path.resolve(__dirname, '../.env.development' )}); dotenv.config({ path: path.resolve(__dirname, '../.env.development' )});
console.log("Activando entorno de -> development"); console.log(`Activando entorno de ->${chalk.green(` DEVELOPMENT `)}`);
} else if (process.env.NODE_ENV === 'stage') { } else if (process.env.NODE_ENV === 'stage') {
dotenv.config({ path: path.resolve(__dirname, '../.env.test' )}); dotenv.config({ path: path.resolve(__dirname, '../.env.test' )});
console.log("Activando entorno de -> testing"); console.log(`Activando entorno de ->${chalk.yellow(` TESTING `)}`);
} else if (process.env.NODE_ENV === 'production') { } else if (process.env.NODE_ENV === 'production') {
dotenv.config({ path: path.resolve(__dirname, '../.env' )}); dotenv.config({ path: path.resolve(__dirname, '../.env.production' )});
console.log("Activando entorno de -> producción"); console.log(`Activando entorno de ->${chalk.red(` PRODUCTION `)}`);
} }
} catch (error) { } catch (error) {
console.log("A ocurrido un error al seleccionar el entorno. \nError: " + error); console.log("A ocurrido un error al seleccionar el entorno. \nError: " + error);
} }
// Renderiado // Configuración de renderizado
const app = express(); const app = express();
app.use(cors()); app.use(cors());
app.use(express.json()); app.use(express.json());
app.set('trust proxy', true);
app.use(express.static(path.join(__dirname, 'pages')));
// Configuración de conexión PostgreSQL // Configuración de conexión PostgreSQL
@@ -51,29 +56,144 @@ async function verificarConexion() {
try { try {
const client = await pool.connect(); const client = await pool.connect();
const res = await client.query('SELECT NOW() AS hora'); const res = await client.query('SELECT NOW() AS hora');
console.log('Conexión con la base de datos fue exitosa.'); console.log(`\nConexión con la base de datos ${chalk.green(process.env.DB_NAME)} fue exitosa.`);
console.log('Fecha y hora actual de la base de datos:', res.rows[0].hora); console.log('Fecha y hora actual de la base de datos:', res.rows[0].hora);
client.release(); // liberar el cliente de nuevo al pool client.release(); // liberar el cliente de nuevo al pool
} catch (error) { } catch (error) {
console.error('Error al conectar con la base de datos al iniciar:', error.message); console.error('Error al conectar con la base de datos al iniciar:', error.message);
console.error(`Troubleshooting:\n1. Compruebe que las bases de datos se iniciaron correctamente.\n2. Verifique las credenciales y puertos de acceso a la base de datos.\n3. Si está conectandose a una base de datos externa a localhost, verifique las reglas del firewal de entrada y salida de ambos dispositivos.`);
} }
} }
// === Servir páginas estáticas === // === Servir páginas estáticas ===
app.use('/pages', express.static(path.join(__dirname, 'pages')));
// Rutas de conveniencia para abrir cada página rápido:
// (Opcional: puedes usar directamente /pages/roles.html, etc.)
app.get('/',(req, res) => res.sendFile(path.join(__dirname, 'pages', 'index.html'))); app.get('/',(req, res) => res.sendFile(path.join(__dirname, 'pages', 'index.html')));
app.get('/planes', async (req, res) => {
try {
const result = await pool.query(`
SELECT id, nombre, descripcion, precio
FROM plan
WHERE activo = true
ORDER BY id
`);
res.json(result.rows);
} catch (err) {
console.error(err);
res.status(500).json({ error: 'Error al cargar planes' });
}
});
app.post('/api/registro', async (req, res) => {
const {
nombre_empresa,
rut,
correo,
telefono,
direccion,
logo,
clave_acceso,
plan_id
} = req.body;
try {
const client = await pool.connect();
// 1. Hashear la contraseña
const hash = await bcrypt.hash(clave_acceso, 10);
// 2. Insertar el tenant
const result = await client.query(`
INSERT INTO tenant (
nombre_empresa, rut, correo, telefono, direccion, logo,
clave_acceso, plan_id, nombre_base_datos
) VALUES (
$1, $2, $3, $4, $5, $6,
$7, $8, 'TEMPORAL'
)
RETURNING uuid;
`, [
nombre_empresa, rut, correo, telefono, direccion, logo,
hash, plan_id
]);
const uuid = result.rows[0].uuid;
const nombre_base_datos = `tenantdb_${uuid}`.replace(/-/g, '').substring(0, 24); // ajustamos para longitud segura
// 3. Actualizar el campo nombre_base_datos
await client.query(`
UPDATE tenant SET nombre_base_datos = $1 WHERE uuid = $2
`, [nombre_base_datos, uuid]);
client.release();
return res.status(201).json({
message: 'Tenant registrado correctamente',
uuid,
nombre_base_datos
});
} catch (err) {
console.error(err);
return res.status(500).json({ error: 'Error al registrar tenant' });
}
});
app.post('/api/login', async (req, res) => {
const { correo, clave_acceso } = req.body;
try {
const client = await pool.connect();
const result = await client.query(`
SELECT uuid, clave_acceso, nombre_empresa, nombre_base_datos
FROM tenant
WHERE correo = $1 AND estado = true
`, [correo]);
client.release();
if (result.rows.length === 0) {
return res.status(401).json({ error: 'Correo no registrado o inactivo' });
}
const tenant = result.rows[0];
const coincide = await bcrypt.compare(clave_acceso, tenant.clave_acceso);
if (!coincide) {
return res.status(401).json({ error: 'Clave incorrecta' });
}
return res.status(200).json({
message: 'Login correcto',
uuid: tenant.uuid,
nombre_empresa: tenant.nombre_empresa,
base_datos: tenant.nombre_base_datos
});
} catch (err) {
console.error(err);
return res.status(500).json({ error: 'Error al validar login' });
}
});
// Colores personalizados
let primaryColor = chalk.hex('#'+`${process.env.COL_PRI}`);
let secondaryColor = chalk.hex('#'+`${process.env.COL_SEC}`);
// let backgroundColor = chalk.hex('#'+`${process.env.COL_BG}`);
app.use(expressLayouts); app.use(expressLayouts);
// Iniciar servidor // Iniciar servidor
app.listen( process.env.PORT, () => { app.listen( process.env.PORT, () => {
console.log(`Servidor corriendo en http://localhost:${process.env.PORT}`); console.log(`Servidor de ${chalk.yellow('autenticación')} de ${secondaryColor('SuiteCoffee')} corriendo en ${chalk.yellow(`http://localhost:${process.env.PORT}\n`)}` );
console.log('Estableciendo conexión con la db...'); console.log(chalk.grey(`Comprobando accesibilidad a la db ${chalk.green(process.env.DB_NAME)} del host ${chalk.white(`${process.env.DB_HOST}`)} ...`));
verificarConexion(); verificarConexion();
}); });
app.get("/health", async (req, res) => {
// Podés chequear DB aquí. 200 = healthy; 503 = not ready.
res.status(200).json({ status: "ok" });
});
+136 -27
View File
@@ -1,45 +1,154 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="es"> <html lang="es">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>Login Bootstrap</title> <title>SuiteCoffee - Autenticación</title>
<!-- Bootstrap CDN -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
</head> </head>
<body class="bg-light d-flex justify-content-center align-items-center vh-100"> <body class="bg-light d-flex justify-content-center align-items-center vh-100">
<div class="card shadow p-4" style="width: 100%; max-width: 350px;"> <div class="card shadow p-4" style="width: 100%; max-width: 400px;">
<h4 class="text-center mb-4">Iniciar Sesión</h4> <h4 class="text-center mb-3" id="form-title">Iniciar Sesión</h4>
<form id="form-login"> <!-- Mensajes -->
<div id="mensaje" class="alert d-none" role="alert"></div>
<!-- Formulario compartido -->
<form id="formulario">
<div id="registro-extra" style="display: none;">
<div class="mb-2">
<input type="text" class="form-control" id="nombre_empresa" placeholder="Nombre de la empresa" required>
</div>
<div class="mb-2">
<input type="text" class="form-control" id="rut" placeholder="RUT (opcional)" required>
</div>
<div class="mb-2">
<input type="text" class="form-control" id="telefono" placeholder="Teléfono">
</div>
<div class="mb-2">
<input type="text" class="form-control" id="direccion" placeholder="Dirección">
</div>
<div class="mb-2">
<input type="text" class="form-control" id="logo" placeholder="Logo URL (opcional)">
</div>
<div class="mb-2">
<select class="form-select" id="plan_id" required>
<option value="">Cargando planes...</option>
</select>
</div>
</div>
<div class="mb-2">
<input type="email" class="form-control" id="correo" placeholder="Correo" required>
</div>
<div class="mb-3"> <div class="mb-3">
<label for="usuario" class="form-label">Usuario</label> <input type="password" class="form-control" id="clave" placeholder="Contraseña" required>
<input type="text" class="form-control" id="usuario" placeholder="Ingrese su usuario" required>
</div> </div>
<div class="mb-3"> <button type="submit" class="btn btn-primary w-100" id="btn-submit">Entrar</button>
<label for="clave" class="form-label">Contraseña</label>
<input type="password" class="form-control" id="clave" placeholder="Ingrese su contraseña" required>
</div>
<div class="d-flex justify-content-between align-items-center mb-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="recordarme">
<label class="form-check-label" for="recordarme">
Recordarme
</label>
</div>
<a href="#" class="small">¿Olvidaste tu contraseña?</a>
</div>
<button type="submit" class="btn btn-primary w-100">Entrar</button>
</form> </form>
<div class="text-center mt-3">
<button class="btn btn-link btn-sm" id="toggle-mode">¿No tienes cuenta? Regístrate</button>
</div>
</div> </div>
<!-- Bootstrap JS (opcional) --> <script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script> const form = document.getElementById('formulario');
const mensaje = document.getElementById('mensaje');
const toggleModeBtn = document.getElementById('toggle-mode');
const registroExtra = document.getElementById('registro-extra');
const formTitle = document.getElementById('form-title');
const btnSubmit = document.getElementById('btn-submit');
let modoRegistro = false;
toggleModeBtn.addEventListener('click', () => {
modoRegistro = !modoRegistro;
registroExtra.style.display = modoRegistro ? 'block' : 'none';
formTitle.textContent = modoRegistro ? 'Registrar Cuenta' : 'Iniciar Sesión';
btnSubmit.textContent = modoRegistro ? 'Registrarse' : 'Entrar';
toggleModeBtn.textContent = modoRegistro ? '¿Ya tienes cuenta? Inicia sesión' : '¿No tienes cuenta? Regístrate';
if (modoRegistro) {
cargarPlanes(); // ✅ ahora sí se ejecutará correctamente
}
});
form.addEventListener('submit', async (e) => {
e.preventDefault();
mensaje.classList.add('d-none');
const datos = {
correo: document.getElementById('correo').value,
clave_acceso: document.getElementById('clave').value
};
if (modoRegistro) {
Object.assign(datos, {
nombre_empresa: document.getElementById('nombre_empresa').value,
rut: document.getElementById('rut').value,
telefono: document.getElementById('telefono').value,
direccion: document.getElementById('direccion').value,
logo: document.getElementById('logo').value,
plan_id: document.getElementById('plan_id').value
});
}
try {
const url = modoRegistro ? '/api/registro' : '/api/login';
const res = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(datos)
});
const resultado = await res.json();
if (!res.ok) {
throw new Error(resultado.error || 'Error inesperado');
}
mensaje.className = 'alert alert-success';
mensaje.textContent = resultado.message || (modoRegistro ? 'Registro exitoso' : 'Inicio exitoso');
mensaje.classList.remove('d-none');
if (!modoRegistro) {
// Redirigir a dashboard, por ejemplo
// window.location.href = `/dashboard?tenant=${resultado.uuid}`;
}
} catch (err) {
mensaje.className = 'alert alert-danger';
mensaje.textContent = err.message;
mensaje.classList.remove('d-none');
}
});
// ✅ Ahora la función está declarada correctamente
async function cargarPlanes() {
const select = document.getElementById('plan_id');
select.innerHTML = '<option value="">Cargando planes...</option>';
try {
const res = await fetch('/planes');
const planes = await res.json();
select.innerHTML = '<option value="">Seleccione un plan</option>';
planes.forEach(plan => {
const opt = document.createElement('option');
opt.value = plan.id;
opt.textContent = plan.nombre.charAt(0).toUpperCase() + plan.nombre.slice(1);
select.appendChild(opt);
});
} catch (err) {
select.innerHTML = '<option value="">Error al cargar planes</option>';
console.error('Error cargando planes:', err);
}
}
</script>
</body> </body>
</html> </html>
+386
View File
@@ -0,0 +1,386 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import sys
import subprocess
from shutil import which
PROJECT_ROOT = os.path.abspath(os.getcwd())
# === Archivos Compose (entornos) ===
BASE_COMPOSE = os.path.join(PROJECT_ROOT, "compose.yaml")
DEV_COMPOSE = os.path.join(PROJECT_ROOT, "compose.dev.yaml")
PROD_COMPOSE = os.path.join(PROJECT_ROOT, "compose.prod.yaml")
# === Archivos Compose (globales) ===
NPM_COMPOSE = os.path.join(PROJECT_ROOT, "compose.npm.yaml")
DBEAVER_COMPOSE = os.path.join(PROJECT_ROOT, "compose.dbeaver.yaml")
# Archivos .env
ENV_FILES = {
"development": ".env.development",
"production": ".env.production",
}
# Nombres de proyecto para permitir DEV y PROD simultáneos
def _base_project():
return os.path.basename(PROJECT_ROOT).lower() or "composeproj"
PROJECT_NAMES = {
"development": f"{_base_project()}_dev",
"production": f"{_base_project()}_prod",
}
# Nombre de proyecto global (ambos yaml globales usan name: suitecoffee)
GLOBAL_PROJECT_NAME = "suitecoffee"
# ---------- Utilidades ----------
def check_prereqs():
if which("docker") is None:
fail("No se encontró 'docker' en el PATH.")
try:
run(["docker", "compose", "version"], check=True, capture_output=True)
except Exception:
fail("No se pudo ejecutar 'docker compose'. Asegúrate de tener Docker Compose v2.")
def run(cmd, check=False, capture_output=False):
return subprocess.run(cmd, check=check, capture_output=capture_output, text=True)
def compose_files_args(env_key):
"""
Devuelve los -f correctos según el entorno (dev/prod) + base.
"""
if not os.path.exists(BASE_COMPOSE):
fail("No se encontró compose.yaml en la raíz del proyecto.")
args = ["-f", BASE_COMPOSE]
if env_key == "development":
if not os.path.exists(DEV_COMPOSE):
fail("No se encontró compose.dev.yaml.")
args += ["-f", DEV_COMPOSE]
elif env_key == "production":
if not os.path.exists(PROD_COMPOSE):
fail("No se encontró compose.prod.yaml.")
args += ["-f", PROD_COMPOSE]
else:
fail(f"Entorno desconocido: {env_key}")
return args
def compose_files_args_global(kind):
"""
Devuelve los -f correctos para servicios globales (npm/dbeaver).
"""
if kind == "npm":
if not os.path.exists(NPM_COMPOSE):
fail("No se encontró compose.npm.yaml.")
return ["-f", NPM_COMPOSE]
elif kind == "dbeaver":
if not os.path.exists(DBEAVER_COMPOSE):
fail("No se encontró compose.dbeaver.yaml.")
return ["-f", DBEAVER_COMPOSE]
else:
fail(f"Servicio global desconocido: {kind}")
def env_file_path(env_key):
fname = ENV_FILES.get(env_key)
if not fname:
return None
path = os.path.join(PROJECT_ROOT, fname)
return path if os.path.exists(path) else None
def compose_cmd(base_args, env_key, env_file=None, project_name=None):
"""
Construye: docker compose -f base -f env --env-file ... -p <name> <COMANDO> [OPCIONES]
(importante: --env-file y -p son opciones globales y van antes del subcomando)
"""
cmd = ["docker", "compose"]
cmd += compose_files_args(env_key)
if env_file:
cmd += ["--env-file", env_file]
if project_name:
cmd += ["-p", project_name]
cmd += base_args # ["up","-d","--force-recreate"] o ["ps","--status","running","-q"]
return cmd
def compose_cmd_global(base_args, kind, project_name=GLOBAL_PROJECT_NAME):
"""
Comandos para servicios globales (npm/dbeaver):
docker compose -f compose.<kind>.yaml -p suitecoffee <COMANDO> ...
"""
cmd = ["docker", "compose"]
cmd += compose_files_args_global(kind)
if project_name:
cmd += ["-p", project_name]
cmd += base_args
return cmd
def yes_no(prompt, default="n"):
default = default.lower()
hint = "[Y/n]" if default == "y" else "[y/N]"
while True:
resp = input(f"{prompt} {hint} ").strip().lower()
if not resp:
return default == "y"
if resp in ("y", "yes", "s", "si", ""):
return True
if resp in ("n", "no"):
return False
print("Respuesta no reconocida. Por favor, responde con 'y' o 'n'.")
def print_header(title):
print("\n" + "=" * 60)
print(title)
print("=" * 60 + "\n")
def info(msg): print(f"{msg}")
def ok(msg): print(f"{msg}")
def warn(msg): print(f"! {msg}")
def fail(msg):
print(f"{msg}")
sys.exit(1)
# ---------- Helpers globales (servicios por archivo y ps por labels) ----------
def list_services_from_compose_file(compose_path):
"""Obtiene la lista de servicios definidos en un archivo compose específico."""
cmd = ["docker", "compose", "-f", compose_path, "config", "--services"]
proc = run(cmd, capture_output=True)
if proc.returncode != 0:
return []
return [s.strip() for s in proc.stdout.splitlines() if s.strip()]
def list_services_global(kind):
compose_path = NPM_COMPOSE if kind == "npm" else DBEAVER_COMPOSE
return list_services_from_compose_file(compose_path)
def docker_ps_by_labels(project, service=None, running_only=True):
"""Lista contenedores por labels de compose (project/service)."""
cmd = ["docker", "ps"]
if running_only:
# por defecto docker ps ya lista solo running; se deja explícito por claridad
pass
cmd += ["--filter", f"label=com.docker.compose.project={project}"]
if service:
cmd += ["--filter", f"label=com.docker.compose.service={service}"]
cmd += ["-q"]
proc = run(cmd, capture_output=True)
if proc.returncode != 0:
return []
return [l.strip() for l in proc.stdout.splitlines() if l.strip()]
# ---------- Acciones (entornos) ----------
def bring_up(env_key, force_recreate=False):
env_path = env_file_path(env_key)
if not env_path:
warn(f"No se encontró archivo de entorno para '{env_key}'. Continuando sin --env-file.")
pname = PROJECT_NAMES.get(env_key)
base_args = ["up", "-d"]
if force_recreate:
base_args.append("--force-recreate")
cmd = compose_cmd(base_args, env_key=env_key, env_file=env_path, project_name=pname)
info("Ejecutando: " + " ".join(cmd))
proc = run(cmd)
if proc.returncode == 0:
ok(f"Entorno '{env_key}' levantado correctamente.")
else:
fail(f"Fallo al levantar entorno '{env_key}'. Código: {proc.returncode}")
def bring_down(env_key):
env_path = env_file_path(env_key)
pname = PROJECT_NAMES.get(env_key)
cmd = compose_cmd(["down"], env_key=env_key, env_file=env_path, project_name=pname)
info("Ejecutando: " + " ".join(cmd))
proc = run(cmd)
if proc.returncode == 0:
ok(f"Entorno '{env_key}' detenido correctamente.")
else:
fail(f"Fallo al detener entorno '{env_key}'. Código: {proc.returncode}")
def running_ids(env_key):
env_path = env_file_path(env_key)
pname = PROJECT_NAMES.get(env_key)
cmd = compose_cmd(["ps", "--status", "running", "-q"], env_key=env_key, env_file=env_path, project_name=pname)
proc = run(cmd, capture_output=True)
if proc.returncode != 0:
return []
return [l.strip() for l in proc.stdout.splitlines() if l.strip()]
# ---------- Acciones (globales) ----------
def bring_up_global(kind, force_recreate=False):
base_args = ["up", "-d"]
if force_recreate:
base_args.append("--force-recreate")
cmd = compose_cmd_global(base_args, kind=kind)
info("Ejecutando: " + " ".join(cmd))
proc = run(cmd)
if proc.returncode == 0:
ok(f"Servicio global '{kind}' levantado correctamente.")
else:
fail(f"Fallo al levantar servicio global '{kind}'. Código: {proc.returncode}")
def bring_down_global(kind, remove=False):
"""
Apaga SOLO los servicios definidos en el compose global indicado.
- Primero 'stop <servicios>'.
- Opcionalmente, 'rm -f <servicios>' si remove=True.
"""
services = list_services_global(kind)
if not services:
warn(f"No se encontraron servicios en compose.{kind}.yaml.")
return
# stop específico
cmd = compose_cmd_global(["stop"] + services, kind=kind)
info("Ejecutando: " + " ".join(cmd))
proc = run(cmd)
if proc.returncode != 0:
fail(f"Fallo al detener servicios de '{kind}'. Código: {proc.returncode}")
if remove:
cmd_rm = compose_cmd_global(["rm", "-f"] + services, kind=kind)
info("Ejecutando: " + " ".join(cmd_rm))
proc_rm = run(cmd_rm)
if proc_rm.returncode != 0:
fail(f"Fallo al remover servicios de '{kind}'. Código: {proc_rm.returncode}")
ok(f"Servicio(s) global(es) '{kind}' detenido(s).")
def running_ids_global(kind):
"""
Detecta si el compose global está corriendo revisando contenedores por servicio,
filtrando por labels (project+service).
"""
services = list_services_global(kind)
ids = []
for svc in services:
ids += docker_ps_by_labels(GLOBAL_PROJECT_NAME, service=svc, running_only=True)
# eliminar duplicados
return list(dict.fromkeys(ids))
# ---------- Estado y flujo ----------
def detect_status_summary():
dev_running = running_ids("development")
prod_running = running_ids("production")
npm_running = running_ids_global("npm")
dbeaver_running = running_ids_global("dbeaver")
print_header("Estado actual")
info(f"DESARROLLO: {len(dev_running)} contenedor(es) en ejecución.")
info(f"PRODUCCIÓN: {len(prod_running)} contenedor(es) en ejecución.")
info(f"NPM (global): {len(npm_running)} contenedor(es) en ejecución.")
info(f"DBEAVER (global): {len(dbeaver_running)} contenedor(es) en ejecución.\n")
return bool(dev_running), bool(prod_running), bool(npm_running), bool(dbeaver_running)
def detect_and_optionally_shutdown():
"""Muestra estado y ofrece (opcional) apagar dev/prod.
Los servicios globales se gestionan desde el menú principal (levantar/apagar).
"""
dev_on, prod_on, _npm_on, _dbeaver_on = detect_status_summary()
options = []
if dev_on:
options.append(("1", "Apagar entorno de DESARROLLO", "development"))
if prod_on:
options.append(("2", "Apagar entorno de PRODUCCIÓN", "production"))
options.append(("3", "Continuar sin detener nada", None))
if len(options) == 1:
return
print("Selecciona una opción:")
for opt in options:
key, label = opt[0], opt[1]
print(f" {key}) {label}")
while True:
choice = input("> ").strip()
selected = next((opt for opt in options if opt[0] == choice), None)
if not selected:
print("Opción inválida.")
continue
if choice == "3" or selected[2] is None:
ok("Continuamos sin detener nada.")
return
env_key = selected[2]
bring_down(env_key)
return
def main_menu():
# Consultar estado de globales para decidir si mostrar opciones de "Levantar" o "Apagar"
_dev_on, _prod_on, npm_on, dbeaver_on = detect_status_summary()
print_header("Gestor de entornos Docker Compose")
print("Selecciona una opción:")
print(" 1) Levantar entorno de DESARROLLO")
print(" 2) Levantar entorno de PRODUCCIÓN")
dynamic_keys = {}
next_key = 3
# NPM: opción según estado
if not npm_on:
print(f" {next_key}) Levantar NPM (compose.npm.yaml)")
dynamic_keys[str(next_key)] = ("global_up", "npm")
else:
print(f" {next_key}) Apagar NPM (compose.npm.yaml)")
dynamic_keys[str(next_key)] = ("global_down", "npm")
next_key += 1
# DBEAVER: opción según estado
if not dbeaver_on:
print(f" {next_key}) Levantar DBEAVER (compose.dbeaver.yaml)")
dynamic_keys[str(next_key)] = ("global_up", "dbeaver")
else:
print(f" {next_key}) Apagar DBEAVER (compose.dbeaver.yaml)")
dynamic_keys[str(next_key)] = ("global_down", "dbeaver")
next_key += 1
# Salir
print(f" {next_key}) Salir")
exit_key = str(next_key)
while True:
choice = input("> ").strip()
if choice == "1":
force = yes_no("¿Usar --force-recreate para DESARROLLO?", default="n")
bring_up("development", force_recreate=force)
return
elif choice == "2":
force = yes_no("¿Usar --force-recreate para PRODUCCIÓN?", default="n")
bring_up("production", force_recreate=force)
return
elif choice in dynamic_keys:
action, kind = dynamic_keys[choice]
if action == "global_up":
force = yes_no(f"¿Usar --force-recreate para {kind.upper()}?", default="n")
bring_up_global(kind, force_recreate=force)
return
elif action == "global_down":
remove = yes_no(f"¿También remover contenedores de {kind.upper()}? (rm -f)", default="n")
bring_down_global(kind, remove=remove)
return
elif choice == exit_key:
ok("Saliendo.")
sys.exit(0)
else:
print("Opción inválida. Elige una de las opciones listadas.")
def main():
try:
check_prereqs()
# Mostrar estado y permitir opcionalmente apagar dev/prod
detect_and_optionally_shutdown()
# Menú de gestión (incluye globales: levantar o apagar según estado)
main_menu()
except KeyboardInterrupt:
print("\n")
ok("Interrumpido por el usuario (Ctrl+C). Saliendo.")
sys.exit(0)
except Exception as e:
fail(f"Ocurrió un error inesperado: {e}")
if __name__ == "__main__":
main()