Se pudo conectar satisfactoriamente a la base de datos tenantdb_1 y suitecoffee-db.
En ./services/app/ Se sirvierons los HTML de la carpeta /pages. Se crearon los endpoints REST para crear y listar roles, usuarios, categorias, productos.
This commit is contained in:
@@ -1,31 +0,0 @@
|
||||
# Dockerfile.prod
|
||||
FROM node:23-slim
|
||||
|
||||
# Definir variables de entorno con valores predeterminados
|
||||
ARG NODE_ENV=production
|
||||
ARG PORT=8080
|
||||
|
||||
# Definir las variables de entorno dentro del contenedor
|
||||
ENV NODE_ENV=${NODE_ENV}
|
||||
ENV PORT=${PORT}
|
||||
|
||||
# Crea directorio de trabajo
|
||||
WORKDIR /app
|
||||
|
||||
# Copia solo archivos necesarios para prod
|
||||
COPY package*.json ./
|
||||
|
||||
# Instala solo dependencias de producción
|
||||
RUN npm install --omit=dev
|
||||
|
||||
# Copia el resto de la app
|
||||
COPY . .
|
||||
|
||||
# Expone el puerto
|
||||
EXPOSE ${PORT}
|
||||
|
||||
# Ejecutar el servidor con nodemon en desarrollo, o con node en producción
|
||||
CMD ["npm", "start"]
|
||||
|
||||
# # Corre la app normalmente
|
||||
# CMD ["node", "src/index.js"]
|
||||
@@ -1,28 +1,22 @@
|
||||
# Dockerfile.dev
|
||||
FROM node:23-slim
|
||||
FROM node:20.17
|
||||
|
||||
# Definir variables de entorno con valores predeterminados
|
||||
ARG NODE_ENV=development
|
||||
ARG PORT=3000
|
||||
|
||||
# Definir las variables de entorno dentro del contenedor
|
||||
ENV NODE_ENV=${NODE_ENV}
|
||||
ENV PORT=${PORT}
|
||||
|
||||
# Crea directorio de trabajo
|
||||
WORKDIR /app
|
||||
|
||||
# Copia archivos de configuración primero para aprovechar el cache
|
||||
COPY package*.json ./
|
||||
|
||||
# Instala dependencias (incluye devDependencies)
|
||||
RUN npm install
|
||||
# Instala dependencias
|
||||
RUN npm i express pg dotenv cors
|
||||
RUN npm i --save-dev nodemon
|
||||
|
||||
# Copia el resto de la app
|
||||
COPY . .
|
||||
|
||||
# Expone el puerto
|
||||
EXPOSE ${PORT}
|
||||
EXPOSE 3000
|
||||
|
||||
# Usa nodemon para hot reload si lo tenés
|
||||
CMD ["npx", "nodemon", "src/index.js"]
|
||||
CMD ["npm", "run", "dev"]
|
||||
Generated
+1342
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "app",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "NODE_ENV=production node ./src/index.js",
|
||||
"dev": "NODE_ENV=development node ./src/index.js",
|
||||
"test": "NODE_ENV=stage node ./src/index.js"
|
||||
},
|
||||
"author": "Mateo Saldain",
|
||||
"license": "ISC",
|
||||
"type": "module",
|
||||
"devDependencies": {
|
||||
"cross-env": "^10.0.0",
|
||||
"nodemon": "^3.1.10"
|
||||
},
|
||||
"dependencies": {
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^17.2.1",
|
||||
"express": "^5.1.0",
|
||||
"express-ejs-layouts": "^2.5.1",
|
||||
"pg": "^8.16.3"
|
||||
},
|
||||
"keywords": [],
|
||||
"description": ""
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
// app/src/index.js
|
||||
import express from 'express';
|
||||
import expressLayouts from 'express-ejs-layouts';
|
||||
import cors from 'cors';
|
||||
import { Pool } from 'pg';
|
||||
|
||||
// Rutas
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
// Variables de Entorno
|
||||
import dotenv, { config } from 'dotenv';
|
||||
|
||||
// Obtención de la ruta de la variable de entorno correspondiente a NODE_ENV
|
||||
try {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
dotenv.config({ path: path.resolve(__dirname, '../.env.development' )});
|
||||
console.log("Activando entorno de -> development");
|
||||
} else if (process.env.NODE_ENV === 'stage') {
|
||||
dotenv.config({ path: path.resolve(__dirname, '../.env.test' )});
|
||||
console.log("Activando entorno de -> testing");
|
||||
} else if (process.env.NODE_ENV === 'production') {
|
||||
dotenv.config({ path: path.resolve(__dirname, '../.env' )});
|
||||
console.log("Activando entorno de -> producción");
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("A ocurrido un error al seleccionar el entorno. \nError: " + error);
|
||||
}
|
||||
|
||||
// Renderiado
|
||||
const app = express();
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
|
||||
// Configuración de conexión PostgreSQL
|
||||
|
||||
const dbConfig = {
|
||||
host: process.env.DB_HOST,
|
||||
user: process.env.DB_USER,
|
||||
password: process.env.DB_PASS,
|
||||
database: process.env.DB_NAME,
|
||||
port: process.env.DB_LOCAL_PORT
|
||||
};
|
||||
|
||||
const pool = new Pool(dbConfig);
|
||||
|
||||
|
||||
async function verificarConexion() {
|
||||
try {
|
||||
const client = await pool.connect();
|
||||
const res = await client.query('SELECT NOW() AS hora');
|
||||
console.log('Conexión con la base de datos fue exitosa.');
|
||||
console.log('Fecha y hora actual de la base de datos:', res.rows[0].hora);
|
||||
client.release(); // liberar el cliente de nuevo al pool
|
||||
} catch (error) {
|
||||
console.error('Error al conectar con la base de datos al iniciar:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// === 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('/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('/productos', (req, res) => res.sendFile(path.join(__dirname, 'pages', 'productos.html')));
|
||||
|
||||
|
||||
// Helper de consulta con acquire/release explícito
|
||||
async function q(text, params) {
|
||||
const client = await pool.connect();
|
||||
try {
|
||||
return await client.query(text, params);
|
||||
} finally {
|
||||
client.release();
|
||||
}
|
||||
}
|
||||
|
||||
// === API Roles ===
|
||||
// GET: listar
|
||||
app.get('/api/roles', async (req, res) => {
|
||||
try {
|
||||
const { rows } = await q('SELECT id_rol, nombre FROM roles ORDER BY id_rol ASC');
|
||||
res.json(rows);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
res.status(500).json({ error: 'No se pudo listar roles' });
|
||||
}
|
||||
});
|
||||
|
||||
// POST: crear
|
||||
app.post('/api/roles', async (req, res) => {
|
||||
try {
|
||||
const { nombre } = req.body;
|
||||
if (!nombre || !nombre.trim()) return res.status(400).json({ error: 'Nombre requerido' });
|
||||
const { rows } = await q(
|
||||
'INSERT INTO roles (nombre) VALUES ($1) RETURNING id_rol, nombre',
|
||||
[nombre.trim()]
|
||||
);
|
||||
res.status(201).json(rows[0]);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
// Manejo de único/duplicado
|
||||
if (e.code === '23505') return res.status(409).json({ error: 'El rol ya existe' });
|
||||
res.status(500).json({ error: 'No se pudo crear el rol' });
|
||||
}
|
||||
});
|
||||
|
||||
// === API Usuarios ===
|
||||
// GET: listar
|
||||
app.get('/api/usuarios', async (req, res) => {
|
||||
try {
|
||||
const { rows } = await q(`
|
||||
SELECT id_usuario, documento, img_perfil, nombre, apellido, correo, telefono, fec_nacimiento, activo
|
||||
FROM usuarios
|
||||
ORDER BY id_usuario ASC
|
||||
`);
|
||||
res.json(rows);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
res.status(500).json({ error: 'No se pudo listar usuarios' });
|
||||
}
|
||||
});
|
||||
|
||||
// POST: crear
|
||||
app.post('/api/usuarios', async (req, res) => {
|
||||
try {
|
||||
const { documento, nombre, apellido, correo, telefono, fec_nacimiento } = req.body;
|
||||
if (!nombre || !apellido) return res.status(400).json({ error: 'Nombre y apellido requeridos' });
|
||||
|
||||
const { rows } = await q(`
|
||||
INSERT INTO usuarios (documento, nombre, apellido, correo, telefono, fec_nacimiento)
|
||||
VALUES ($1, $2, $3, $4, $5, $6)
|
||||
RETURNING id_usuario, documento, nombre, apellido, correo, telefono, fec_nacimiento, activo
|
||||
`, [
|
||||
documento || null,
|
||||
nombre.trim(),
|
||||
apellido.trim(),
|
||||
correo || null,
|
||||
telefono || null,
|
||||
fec_nacimiento || null
|
||||
]);
|
||||
|
||||
res.status(201).json(rows[0]);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
if (e.code === '23505') return res.status(409).json({ error: 'Documento/Correo/Teléfono ya existe' });
|
||||
res.status(500).json({ error: 'No se pudo crear el usuario' });
|
||||
}
|
||||
});
|
||||
|
||||
// === API Categorías ===
|
||||
// GET: listar
|
||||
app.get('/api/categorias', async (req, res) => {
|
||||
try {
|
||||
const { rows } = await q('SELECT id_categoria, nombre, visible FROM categorias ORDER BY id_categoria ASC');
|
||||
res.json(rows);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
res.status(500).json({ error: 'No se pudo listar categorías' });
|
||||
}
|
||||
});
|
||||
|
||||
// POST: crear
|
||||
app.post('/api/categorias', async (req, res) => {
|
||||
try {
|
||||
const { nombre, visible } = req.body;
|
||||
if (!nombre || !nombre.trim()) return res.status(400).json({ error: 'Nombre requerido' });
|
||||
const vis = (typeof visible === 'boolean') ? visible : true;
|
||||
|
||||
const { rows } = await q(`
|
||||
INSERT INTO categorias (nombre, visible)
|
||||
VALUES ($1, $2)
|
||||
RETURNING id_categoria, nombre, visible
|
||||
`, [nombre.trim(), vis]);
|
||||
|
||||
res.status(201).json(rows[0]);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
if (e.code === '23505') return res.status(409).json({ error: 'La categoría ya existe' });
|
||||
res.status(500).json({ error: 'No se pudo crear la categoría' });
|
||||
}
|
||||
});
|
||||
|
||||
// === API Productos ===
|
||||
// GET: listar
|
||||
app.get('/api/productos', async (req, res) => {
|
||||
try {
|
||||
const { rows } = await q(`
|
||||
SELECT id_producto, nombre, img_producto, precio, activo, id_categoria
|
||||
FROM productos
|
||||
ORDER BY id_producto ASC
|
||||
`);
|
||||
res.json(rows);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
res.status(500).json({ error: 'No se pudo listar productos' });
|
||||
}
|
||||
});
|
||||
|
||||
// POST: crear
|
||||
app.post('/api/productos', async (req, res) => {
|
||||
try {
|
||||
let { nombre, id_categoria, precio } = req.body;
|
||||
if (!nombre || !nombre.trim()) return res.status(400).json({ error: 'Nombre requerido' });
|
||||
id_categoria = parseInt(id_categoria, 10);
|
||||
precio = parseFloat(precio);
|
||||
if (!Number.isInteger(id_categoria)) return res.status(400).json({ error: 'id_categoria inválido' });
|
||||
if (!(precio >= 0)) return res.status(400).json({ error: 'precio inválido' });
|
||||
|
||||
const { rows } = await q(`
|
||||
INSERT INTO productos (nombre, id_categoria, precio)
|
||||
VALUES ($1, $2, $3)
|
||||
RETURNING id_producto, nombre, precio, activo, id_categoria
|
||||
`, [nombre.trim(), id_categoria, precio]);
|
||||
|
||||
res.status(201).json(rows[0]);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
// FK categories / checks
|
||||
if (e.code === '23503') return res.status(400).json({ error: 'La categoría no existe' });
|
||||
res.status(500).json({ error: 'No se pudo crear el producto' });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
app.use(expressLayouts);
|
||||
// Iniciar servidor
|
||||
app.listen( process.env.PORT, () => {
|
||||
console.log(`Servidor corriendo en http://localhost:${process.env.PORT}`);
|
||||
console.log('Estableciendo conexión con la db...');
|
||||
verificarConexion();
|
||||
});
|
||||
@@ -0,0 +1,70 @@
|
||||
<!doctype html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Categorías</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Categorías</h1>
|
||||
|
||||
<h2>Crear categoría</h2>
|
||||
<form id="form-categoria">
|
||||
<label>Nombre:
|
||||
<input type="text" name="nombre" required />
|
||||
</label>
|
||||
<label>Visible:
|
||||
<select name="visible">
|
||||
<option value="true" selected>Sí</option>
|
||||
<option value="false">No</option>
|
||||
</select>
|
||||
</label>
|
||||
<button type="submit">Guardar</button>
|
||||
</form>
|
||||
|
||||
<h2>Listado</h2>
|
||||
<button id="btn-recargar">Recargar</button>
|
||||
<table border="1" cellpadding="6">
|
||||
<thead><tr><th>ID</th><th>Nombre</th><th>Visible</th></tr></thead>
|
||||
<tbody id="tbody"></tbody>
|
||||
</table>
|
||||
|
||||
<script>
|
||||
const API = '/api/categorias';
|
||||
|
||||
async function listar() {
|
||||
const res = await fetch(API);
|
||||
const data = await res.json();
|
||||
const tbody = document.getElementById('tbody');
|
||||
tbody.innerHTML = '';
|
||||
data.forEach(c => {
|
||||
const tr = document.createElement('tr');
|
||||
tr.innerHTML = `<td>${c.id_categoria}</td><td>${c.nombre}</td><td>${c.visible ? 'Sí' : 'No'}</td>`;
|
||||
tbody.appendChild(tr);
|
||||
});
|
||||
}
|
||||
|
||||
document.getElementById('form-categoria').addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const fd = new FormData(e.target);
|
||||
const nombre = fd.get('nombre').trim();
|
||||
const visible = fd.get('visible') === 'true';
|
||||
if (!nombre) return;
|
||||
const res = await fetch(API, {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type':'application/json'},
|
||||
body: JSON.stringify({ nombre, visible })
|
||||
});
|
||||
if (!res.ok) {
|
||||
const err = await res.json().catch(()=>({error:'Error'}));
|
||||
alert('Error: ' + (err.error || res.statusText));
|
||||
return;
|
||||
}
|
||||
e.target.reset();
|
||||
await listar();
|
||||
});
|
||||
|
||||
document.getElementById('btn-recargar').addEventListener('click', listar);
|
||||
listar();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,106 @@
|
||||
<!doctype html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Productos</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Productos</h1>
|
||||
|
||||
<h2>Crear producto</h2>
|
||||
<form id="form-producto">
|
||||
<div>
|
||||
<label>Nombre:
|
||||
<input name="nombre" type="text" required />
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label>Precio:
|
||||
<input name="precio" type="number" step="0.01" min="0" required />
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label>Categoría:
|
||||
<select name="id_categoria" id="sel-categoria" required></select>
|
||||
</label>
|
||||
</div>
|
||||
<button type="submit">Guardar</button>
|
||||
</form>
|
||||
|
||||
<h2>Listado</h2>
|
||||
<button id="btn-recargar">Recargar</button>
|
||||
<table border="1" cellpadding="6">
|
||||
<thead>
|
||||
<tr><th>ID</th><th>Nombre</th><th>Precio</th><th>Activo</th><th>ID Categoría</th></tr>
|
||||
</thead>
|
||||
<tbody id="tbody"></tbody>
|
||||
</table>
|
||||
|
||||
<script>
|
||||
const API = '/api/productos';
|
||||
const API_CAT = '/api/categorias';
|
||||
|
||||
async function cargarCategorias() {
|
||||
const res = await fetch(API_CAT);
|
||||
const data = await res.json();
|
||||
const sel = document.getElementById('sel-categoria');
|
||||
sel.innerHTML = '<option value="" disabled selected>Seleccione...</option>';
|
||||
data.forEach(c => {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = c.id_categoria;
|
||||
opt.textContent = `${c.id_categoria} - ${c.nombre}`;
|
||||
sel.appendChild(opt);
|
||||
});
|
||||
}
|
||||
|
||||
async function listar() {
|
||||
const res = await fetch(API);
|
||||
const data = await res.json();
|
||||
const tbody = document.getElementById('tbody');
|
||||
tbody.innerHTML = '';
|
||||
data.forEach(p => {
|
||||
const tr = document.createElement('tr');
|
||||
tr.innerHTML = `
|
||||
<td>${p.id_producto}</td>
|
||||
<td>${p.nombre}</td>
|
||||
<td>${Number(p.precio).toFixed(2)}</td>
|
||||
<td>${p.activo ? 'Sí' : 'No'}</td>
|
||||
<td>${p.id_categoria}</td>
|
||||
`;
|
||||
tbody.appendChild(tr);
|
||||
});
|
||||
}
|
||||
|
||||
document.getElementById('form-producto').addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const fd = new FormData(e.target);
|
||||
const payload = {
|
||||
nombre: fd.get('nombre').trim(),
|
||||
precio: parseFloat(fd.get('precio')),
|
||||
id_categoria: parseInt(fd.get('id_categoria'), 10)
|
||||
};
|
||||
if (!payload.nombre || isNaN(payload.precio) || isNaN(payload.id_categoria)) return;
|
||||
|
||||
const res = await fetch(API, {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type':'application/json'},
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
if (!res.ok) {
|
||||
const err = await res.json().catch(()=>({error:'Error'}));
|
||||
alert('Error: ' + (err.error || res.statusText));
|
||||
return;
|
||||
}
|
||||
e.target.reset();
|
||||
await listar();
|
||||
});
|
||||
|
||||
document.getElementById('btn-recargar').addEventListener('click', listar);
|
||||
|
||||
(async () => {
|
||||
await cargarCategorias();
|
||||
await listar();
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,62 @@
|
||||
<!doctype html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Roles</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Roles</h1>
|
||||
|
||||
<h2>Crear rol</h2>
|
||||
<form id="form-rol">
|
||||
<label>Nombre del rol:
|
||||
<input type="text" name="nombre" required />
|
||||
</label>
|
||||
<button type="submit">Guardar</button>
|
||||
</form>
|
||||
|
||||
<h2>Listado</h2>
|
||||
<button id="btn-recargar">Recargar</button>
|
||||
<table border="1" cellpadding="6">
|
||||
<thead><tr><th>ID</th><th>Nombre</th></tr></thead>
|
||||
<tbody id="tbody"></tbody>
|
||||
</table>
|
||||
|
||||
<script>
|
||||
const API = '/api/roles';
|
||||
|
||||
async function listar() {
|
||||
const res = await fetch(API);
|
||||
const data = await res.json();
|
||||
const tbody = document.getElementById('tbody');
|
||||
tbody.innerHTML = '';
|
||||
data.forEach(r => {
|
||||
const tr = document.createElement('tr');
|
||||
tr.innerHTML = `<td>${r.id_rol}</td><td>${r.nombre}</td>`;
|
||||
tbody.appendChild(tr);
|
||||
});
|
||||
}
|
||||
|
||||
document.getElementById('form-rol').addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const nombre = e.target.nombre.value.trim();
|
||||
if (!nombre) return;
|
||||
const res = await fetch(API, {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type':'application/json'},
|
||||
body: JSON.stringify({ nombre })
|
||||
});
|
||||
if (!res.ok) {
|
||||
const err = await res.json().catch(()=>({error:'Error'}));
|
||||
alert('Error: ' + (err.error || res.statusText));
|
||||
return;
|
||||
}
|
||||
e.target.reset();
|
||||
await listar();
|
||||
});
|
||||
|
||||
document.getElementById('btn-recargar').addEventListener('click', listar);
|
||||
listar();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,104 @@
|
||||
<!doctype html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Usuarios</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Usuarios</h1>
|
||||
|
||||
<h2>Crear usuario</h2>
|
||||
<form id="form-usuario">
|
||||
<div>
|
||||
<label>Documento:
|
||||
<input name="documento" type="text" />
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label>Nombre:
|
||||
<input name="nombre" type="text" required />
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label>Apellido:
|
||||
<input name="apellido" type="text" required />
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label>Correo:
|
||||
<input name="correo" type="email" />
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label>Teléfono:
|
||||
<input name="telefono" type="text" />
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label>Fecha de nacimiento:
|
||||
<input name="fec_nacimiento" type="date" />
|
||||
</label>
|
||||
</div>
|
||||
<button type="submit">Guardar</button>
|
||||
</form>
|
||||
|
||||
<h2>Listado</h2>
|
||||
<button id="btn-recargar">Recargar</button>
|
||||
<table border="1" cellpadding="6">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th><th>Documento</th><th>Nombre</th><th>Apellido</th>
|
||||
<th>Correo</th><th>Teléfono</th><th>Nacimiento</th><th>Activo</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="tbody"></tbody>
|
||||
</table>
|
||||
|
||||
<script>
|
||||
const API = '/api/usuarios';
|
||||
|
||||
async function listar() {
|
||||
const res = await fetch(API);
|
||||
const data = await res.json();
|
||||
const tbody = document.getElementById('tbody');
|
||||
tbody.innerHTML = '';
|
||||
data.forEach(u => {
|
||||
const tr = document.createElement('tr');
|
||||
tr.innerHTML = `
|
||||
<td>${u.id_usuario}</td>
|
||||
<td>${u.documento ?? ''}</td>
|
||||
<td>${u.nombre}</td>
|
||||
<td>${u.apellido}</td>
|
||||
<td>${u.correo ?? ''}</td>
|
||||
<td>${u.telefono ?? ''}</td>
|
||||
<td>${u.fec_nacimiento ? u.fec_nacimiento.substring(0,10) : ''}</td>
|
||||
<td>${u.activo ? 'Sí' : 'No'}</td>
|
||||
`;
|
||||
tbody.appendChild(tr);
|
||||
});
|
||||
}
|
||||
|
||||
document.getElementById('form-usuario').addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const fd = new FormData(e.target);
|
||||
const payload = Object.fromEntries(fd.entries());
|
||||
if (payload.fec_nacimiento === '') delete payload.fec_nacimiento;
|
||||
const res = await fetch(API, {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type':'application/json'},
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
if (!res.ok) {
|
||||
const err = await res.json().catch(()=>({error:'Error'}));
|
||||
alert('Error: ' + (err.error || res.statusText));
|
||||
return;
|
||||
}
|
||||
e.target.reset();
|
||||
await listar();
|
||||
});
|
||||
|
||||
document.getElementById('btn-recargar').addEventListener('click', listar);
|
||||
listar();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,22 @@
|
||||
# Dockerfile.dev
|
||||
FROM node:20.17
|
||||
|
||||
# Definir variables de entorno con valores predeterminados
|
||||
ARG NODE_ENV=development
|
||||
ARG PORT=3000
|
||||
|
||||
# Copia archivos de configuración primero para aprovechar el cache
|
||||
COPY package*.json ./
|
||||
|
||||
# Instala dependencias
|
||||
RUN npm i express pg dotenv cors
|
||||
RUN npm i --save-dev nodemon
|
||||
|
||||
# Copia el resto de la app
|
||||
COPY . .
|
||||
|
||||
# Expone el puerto
|
||||
EXPOSE 3000
|
||||
|
||||
# Usa nodemon para hot reload si lo tenés
|
||||
CMD ["npm", "run", "dev"]
|
||||
Generated
+1462
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "auth",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "NODE_ENV=production node ./src/index.js",
|
||||
"dev": "NODE_ENV=development node ./src/index.js",
|
||||
"test": "NODE_ENV=stage node ./src/index.js"
|
||||
},
|
||||
"author": "Mateo Saldain",
|
||||
"license": "ISC",
|
||||
"type": "module",
|
||||
"devDependencies": {
|
||||
"cross-env": "^10.0.0",
|
||||
"nodemon": "^3.1.10"
|
||||
},
|
||||
"dependencies": {
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^17.2.1",
|
||||
"express": "^5.1.0",
|
||||
"express-ejs-layouts": "^2.5.1",
|
||||
"pg": "^8.16.3"
|
||||
},
|
||||
"keywords": [],
|
||||
"description": ""
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
// auth/src/index.js
|
||||
import express from 'express';
|
||||
import expressLayouts from 'express-ejs-layouts';
|
||||
import cors from 'cors';
|
||||
import { Pool } from 'pg';
|
||||
|
||||
// Rutas
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
// Variables de Entorno
|
||||
import dotenv, { config } from 'dotenv';
|
||||
|
||||
// Obtención de la ruta de la variable de entorno correspondiente a NODE_ENV
|
||||
try {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
dotenv.config({ path: path.resolve(__dirname, '../.env.development' )});
|
||||
console.log("Activando entorno de -> development");
|
||||
} else if (process.env.NODE_ENV === 'stage') {
|
||||
dotenv.config({ path: path.resolve(__dirname, '../.env.test' )});
|
||||
console.log("Activando entorno de -> testing");
|
||||
} else if (process.env.NODE_ENV === 'production') {
|
||||
dotenv.config({ path: path.resolve(__dirname, '../.env' )});
|
||||
console.log("Activando entorno de -> producción");
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("A ocurrido un error al seleccionar el entorno. \nError: " + error);
|
||||
}
|
||||
|
||||
// Renderiado
|
||||
const app = express();
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
|
||||
// Configuración de conexión PostgreSQL
|
||||
|
||||
const dbConfig = {
|
||||
host: process.env.DB_HOST,
|
||||
user: process.env.DB_USER,
|
||||
password: process.env.DB_PASS,
|
||||
database: process.env.DB_NAME,
|
||||
port: process.env.DB_LOCAL_PORT
|
||||
};
|
||||
|
||||
const pool = new Pool(dbConfig);
|
||||
|
||||
|
||||
async function verificarConexion() {
|
||||
try {
|
||||
const client = await pool.connect();
|
||||
const res = await client.query('SELECT NOW() AS hora');
|
||||
console.log('Conexión con la base de datos fue exitosa.');
|
||||
console.log('Fecha y hora actual de la base de datos:', res.rows[0].hora);
|
||||
client.release(); // liberar el cliente de nuevo al pool
|
||||
} catch (error) {
|
||||
console.error('Error al conectar con la base de datos al iniciar:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// === 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.use(expressLayouts);
|
||||
// Iniciar servidor
|
||||
app.listen( process.env.PORT, () => {
|
||||
console.log(`Servidor corriendo en http://localhost:${process.env.PORT}`);
|
||||
console.log('Estableciendo conexión con la db...');
|
||||
verificarConexion();
|
||||
});
|
||||
@@ -0,0 +1,45 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Login Bootstrap</title>
|
||||
<!-- Bootstrap CDN -->
|
||||
<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>
|
||||
<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;">
|
||||
<h4 class="text-center mb-4">Iniciar Sesión</h4>
|
||||
|
||||
<form id="form-login">
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="usuario" class="form-label">Usuario</label>
|
||||
<input type="text" class="form-control" id="usuario" placeholder="Ingrese su usuario" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<!-- Bootstrap JS (opcional) -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user