Todos los Microservicios saludables.

Nuevo microservicio Plugins + cambios a microservicios anteriores, creación de módulos para conexiones a bases de datos y ajustes en las variables de entorno.
This commit is contained in:
2025-10-10 15:11:17 +00:00
parent a31b411437
commit ba6b4fef4f
47 changed files with 5303 additions and 2492 deletions
+62
View File
@@ -0,0 +1,62 @@
# ===== Runtime =====
NODE_ENV=development
PORT=5050
# ===== Session (usa el Redis del stack) =====
# Para DEV podemos reutilizar el Redis de Authentik. En prod conviene uno separado.
SESSION_SECRET=Neon*Mammal*Boaster*Ludicrous*Fender8*Crablike
SESSION_COOKIE_NAME=sc.sid
# ===== DB principal (metadatos de SuiteCoffee) =====
# Usa el alias de red del servicio 'db' (compose: aliases [dev-db])
DB_HOST=dev-db
DB_NAME=dev_suitecoffee_core
DB_PORT=5432
DB_USER=dev-user-suitecoffee
DB_PASS=dev-pass-suitecoffee
CORE_DB_HOST=dev-db
CORE_DB_NAME=dev_suitecoffee_core
CORE_DB_PORT=5432
CORE_DB_USER=dev-user-suitecoffee
CORE_DB_PASS=dev-pass-suitecoffee
# ===== DB tenants (Tenants de SuiteCoffee) =====
TENANTS_HOST=dev-tenants
TENANTS_DB=dev_suitecoffee_tenants
TENANTS_PORT=5432
TENANTS_USER=suitecoffee
TENANTS_PASS=suitecoffee
TENANTS_DB_HOST=dev-tenants
TENANTS_DB_NAME=dev_suitecoffee_tenants
TENANTS_DB_PORT=5432
TENANTS_DB_USER=suitecoffee
TENANTS_DB_PASS=suitecoffee
# ===== Authentik — Admin API (server-to-server dentro de la red) =====
# Usa el alias de red del servicio 'authentik' y su puerto interno 9000
AK_TOKEN=h2apVHbd3ApMcnnSwfQPXbvximkvP8HnUE25ot3zXWuEEtJFaNCcOzDHB6Xw
AK_REDIS_URL=redis://ak-redis:6379
# ===== OIDC (DEBE coincidir con el Provider) =====
# DEV (todo dentro de la red de Docker):
# - El auth service redirige al navegador a este issuer. Si NO tenés reverse proxy hacia Authentik,
# esta URL interna NO será accesible desde el navegador del host. En ese caso, ver nota más abajo.
APP_BASE_URL=https://suitecoffee.uy
OIDC_LOGIN_URL=https://sso.suitecoffee.uy
OIDC_REDIRECT_URI = https://suitecoffee.uy/auth/callback
OIDC_CLIEN_ID=1orMM8vOvf3WkN2FejXYvUFpPtONG0Lx1eMlwIpW
OIDC_CONFIG_URL=https://sso.suitecoffee.uy/application/o/suitecoffee/.well-known/openid-configuration
OIDC_ISSUER=https://sso.suitecoffee.uy/application/o/suitecoffee/
OIDC_ISSUER_DISCOVERY=https://sso.suitecoffee.uy/application/o/suitecoffee/.well-known/openid-configuration
OIDC_AUTHORIZE_URL=https://sso.suitecoffee.uy/application/o/authorize/
OIDC_TOKEN_URL=https://sso.suitecoffee.uy/application/o/token/
OIDC_USERINFO_URL=https://sso.suitecoffee.uy/application/o/userinfo/
OIDC_LOGOUT_URL=https://sso.suitecoffee.uy/application/o/suitecoffee/end-session/
OIDC_JWKS_URL=https://sso.suitecoffee.uy/application/o/suitecoffee/jwks/
View File
+2381
View File
File diff suppressed because it is too large Load Diff
+47
View File
@@ -0,0 +1,47 @@
{
"name": "plugins",
"version": "1.0.0",
"main": "src/index.mjs",
"scripts": {
"start": "NODE_ENV=production node ./src/index.js",
"dev": "NODE_ENV=development npx nodemon ./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": {
"bcrypt": "^6.0.0",
"chalk": "^5.6.0",
"connect-redis": "^9.0.0",
"cookie-parser": "^1.4.7",
"cors": "^2.8.5",
"dotenv": "^17.2.1",
"ejs": "^3.1.10",
"express": "^5.1.0",
"express-ejs-layouts": "^2.5.1",
"express-session": "^1.18.2",
"ioredis": "^5.7.0",
"jose": "^6.1.0",
"jsonwebtoken": "^9.0.2",
"jwks-rsa": "^3.2.0",
"morgan": "^1.10.1",
"node-appwrite": "^20.2.1",
"node-fetch": "^3.3.2",
"pg": "^8.16.3",
"pg-format": "^1.0.4",
"redis": "^5.8.2",
"serve-favicon": "^2.5.1"
},
"imports": {
"#v1Router": "./src/api/v1/routes/routes.js",
"#pages": "./src/pages/pages.js",
"#db": "./src/db/poolSingleton.js"
},
"keywords": [],
"description": ""
}
+82
View File
@@ -0,0 +1,82 @@
// Coneción Singleton a base de datos.
import { Pool } from 'pg';
class DatabaseCore {
constructor() {
if (DatabaseCore.instance) {
return Database.instance;
}
const config = {
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 ? Number(process.env.DB_LOCAL_PORT) : undefined,
ssl: process.env.PGSSL === 'true' ? { rejectUnauthorized: false } : undefined,
};
this.connection = new Pool(config);
DatabaseCore.instance = this;
}
async query(sql, params) {
return this.connection.query(sql,params);
}
async connect() { /* Definida solo para evitar errores */
return this.connection.connect();
}
async getClient() {
return this.connection.connect();
}
async release() {
await this.connection.end();
}
}
class DatabaseTenants {
constructor() {
if (DatabaseTenants.instance) {
return Database.instance;
}
const config = {
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 ? Number(process.env.DB_LOCAL_PORT) : undefined,
ssl: process.env.PGSSL === 'true' ? { rejectUnauthorized: false } : undefined,
};
this.connection = new Pool(config);
DatabaseTenants.instance = this;
}
async query(sql, params) {
return this.connection.query(sql,params);
}
async connect() { /* Definida solo para evitar errores */
return this.connection.connect();
}
async getClient() {
return this.connection.connect();
}
async release() {
await this.connection.end();
}
}
// const db = new Database();
// db.query('SELECT * FROM users');
const poolCore = new DatabaseCore();
const poolTenants = new DatabaseTenants();
export default {poolCore, poolTenants};
export { poolCore, poolTenants };
+140
View File
@@ -0,0 +1,140 @@
// services/plugins/asistencias/index.mjs
// ------------------------------------------------------------
// ------------------------------------------------------------
import 'dotenv/config'; // Variables de Entorno
import express from 'express';
import expressLayouts from 'express-ejs-layouts';
import { poolCore, poolTenants } from '#db'; // dbCore y dbTenants
import path from 'path';
import { fileURLToPath } from 'url';
import cookieParser from 'cookie-parser';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// -----------------------------------------------------------------------------
// Validación de entorno mínimo (ajusta nombres si difieren)
// -----------------------------------------------------------------------------
// Función para verificar que ciertas variables de entorno estén definida
function checkRequiredEnvVars(...requiredKeys) {
const missingKeys = requiredKeys.filter((key) => !process.env[key]); // Filtramos las que NO existen en process.env
if (missingKeys.length > 0) { // Si falta alguna, mostramos una advertencia
console.warn(
`[PLUGIN] No se encontraron las siguientes variables de entorno: \n\n-> ${missingKeys.join('\n-> ')}`+
`\n`
);
}
}
checkRequiredEnvVars(
'PORT',
'CORE_DB_HOST', 'CORE_DB_PORT', 'CORE_DB_NAME',
'TENANTS_DB_HOST', 'TENANTS_DB_PORT', 'TENANTS_DB_NAME'
);
// ----------------------------------------------------------
// Variables del sistema
// ----------------------------------------------------------
const PORT = process.env.PORT;
const CORE_DB_HOST = process.env.CORE_DB_HOST;
const CORE_DB_PORT = process.env.CORE_DB_PORT;
const CORE_DB_NAME = process.env.CORE_DB_NAME;
const TENANTS_DB_HOST = process.env.TENANTS_DB_HOST;
const TENANTS_DB_PORT = process.env.TENANTS_DB_PORT;
const TENANTS_DB_NAME = process.env.TENANTS_DB_NAME;
// ----------------------------------------------------------
// App + Motor de vistas EJS
// ----------------------------------------------------------
const app = express();
app.set('trust proxy', true);
app.set("views", path.join(__dirname, "views"));
app.set("view engine", "ejs");
app.set("layout", "layouts/main");
app.use(express.json());
app.use(express.json({ limit: '1mb' }));
app.use(express.urlencoded({ extended: true }));
app.use(express.static(path.join(__dirname, 'public'))); // Carga de archivos estaticos
app.use(expressLayouts); // Carga los layouts que usara el renderizado
app.use(cookieParser(process.env.SESSION_SECRET));
// ----------------------------------------------------------
// Verificación de conexión
// ----------------------------------------------------------
async function verificarConexionCore() {
try {
console.log(`[PLUGINS] Comprobando accesibilidad a la db ${CORE_DB_NAME} del host ${CORE_DB_HOST} ...`);
const client = await poolCore.connect();
const { rows } = await client.query('SELECT NOW() AS ahora');
console.log(`\n[PLUGINS] Conexión con ${CORE_DB_NAME} OK. Hora DB:`, rows[0].ahora);
client.release();
} catch (error) {
console.error('[PLUGINS] Error al conectar con la base de datos al iniciar:', error.message);
console.error('[PLUGINS] Revisar credenciales, accesos de red y firewall.');
}
}
async function verificarConexionTenants() {
try {
console.log(`[PLUGINS] Comprobando accesibilidad a la db ${TENANTS_DB_NAME} del host ${TENANTS_DB_HOST} ...`);
const client = await poolTenants.connect();
const { rows } = await client.query('SELECT NOW() AS ahora');
console.log(`\n[PLUGINS] Conexión con ${TENANTS_DB_NAME} OK. Hora DB:`, rows[0].ahora);
client.release();
} catch (error) {
console.error('[PLUGINS] Error al conectar con la base de datos al iniciar:', error.message);
console.error('[PLUGINS] Revisar credenciales, accesos de red y firewall.');
}
}
// ----------------------------------------------------------
// Middleware para datos globales
// ----------------------------------------------------------
app.use((req, res, next) => {
res.locals.currentPath = req.path;
res.locals.pageTitle = "SuiteCoffee";
res.locals.pageId = "";
next();
});
// ----------------------------------------------------------
// Inicio del servidor
// ----------------------------------------------------------
app.listen(PORT, () => {
console.log(`[PLUGINS] http://localhost:${PORT}`);
verificarConexionCore();
verificarConexionTenants();
});
// -----------------------------------------------------------------------------
// Healthcheck
// -----------------------------------------------------------------------------
app.get('/health', (_req, res) => {
res.status(200).json({ status: 'ok'}),
console.log(`[PLUGINS] Saludable`)
});