Files
PANEL_BASES_ANEXO24/src/lib/server/controldesk-pg.ts
AlexeerCT 0bf9c9e68d Refactor database configuration and user management
- Updated .env.example to consolidate SQL Server credentials under PANEL_MSSQL_* variables.
- Removed deprecated docker-compose.postgres.yml file.
- Adjusted docker-compose.yml to utilize new SQL Server credential structure.
- Enhanced README.md with Docker build and push instructions.
- Refined database schema in schema.sql to align with new user and permission structures.
- Updated init-database.js to reflect changes in user and session table names.
- Modified user management functions in users.ts to accommodate new database schema.
- Streamlined API routes to utilize PostgreSQL for user and database management.
- Improved error handling and logging in various server routes.
2026-04-14 14:16:11 -05:00

336 lines
9.6 KiB
TypeScript

/**
* Catálogo ControlDesk en PostgreSQL (esquema a24c; DDL lo aprovisiona otra aplicación).
* Devuelve columnas con alias en español para compatibilidad con la UI existente.
*/
import { env } from '$env/dynamic/private';
import { pgPool } from './db';
function schemaName(): string {
const s = 'a24c';
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(s)) return 'a24c';
return s;
}
function qNodes(): string {
const s = schemaName();
return `"${s.replace(/"/g, '""')}"."database_nodes"`;
}
function qUsers(): string {
const s = schemaName();
return `"${s.replace(/"/g, '""')}"."portal_users"`;
}
const ROW_DATABASE_NODE = `
id AS "ID",
node_subnode_key AS "NodoSubNodo",
is_active AS "Activo",
rfc AS "RFC",
legal_name AS "Nombre",
branch_name AS "Sucursal",
notification_email AS "CorreoNotificacion",
server_name AS "ServerName",
database_name AS "BDName"
`;
const ROW_PORTAL_USER = `
id AS "ID",
database_node_id AS "IDNodoSubNodo",
is_authority_client AS "ClienteAutoridad",
full_name AS "Nombre",
username AS "Usuario",
bd_shelter AS "BD_Shelter"
`;
export async function listDatabaseNodes(): Promise<any[]> {
const sql = `SELECT ${ROW_DATABASE_NODE} FROM ${qNodes()}`;
const r = await pgPool.query(sql);
return r.rows;
}
export async function listDatabaseNodesActive(): Promise<any[]> {
const sql = `
SELECT ${ROW_DATABASE_NODE}
FROM ${qNodes()}
WHERE is_active = 1
AND database_name IS NOT NULL
AND TRIM(database_name) <> ''
ORDER BY node_subnode_key
`;
const r = await pgPool.query(sql);
return r.rows;
}
/** Nodos activos con `sql_password` para conectar a SQL Server (no exponer al cliente). */
export async function listDatabaseNodesForMssql(): Promise<any[]> {
const where = `
WHERE is_active = 1
AND database_name IS NOT NULL
AND TRIM(database_name) <> ''
ORDER BY node_subnode_key
`;
try {
const r = await pgPool.query(
`SELECT ${ROW_DATABASE_NODE}, sql_password AS "sql_password" FROM ${qNodes()} ${where}`
);
return r.rows;
} catch (e: any) {
if (e?.code === '42703') {
const r = await pgPool.query(`SELECT ${ROW_DATABASE_NODE} FROM ${qNodes()} ${where}`);
return r.rows.map((row: any) => ({ ...row, sql_password: null }));
}
throw e;
}
}
export async function listClientsCatalog(): Promise<any[]> {
const sql = `
SELECT
id AS "ID",
legal_name AS "Nombre",
node_subnode_key AS "NodoSubNodo",
notification_email AS "CorreoNotificacion",
is_active AS "Activo",
database_name AS "BDName"
FROM ${qNodes()}
`;
const r = await pgPool.query(sql);
return r.rows;
}
export async function listPortalUsers(): Promise<any[]> {
const sql = `SELECT ${ROW_PORTAL_USER} FROM ${qUsers()} ORDER BY id`;
const r = await pgPool.query(sql);
return r.rows;
}
export async function lookupNodeByNodoOrBdName(nodoName: string): Promise<any | null> {
const sql = `
SELECT
legal_name AS "Nombre",
node_subnode_key AS "NodoSubNodo",
rfc AS "RFC"
FROM ${qNodes()}
WHERE LOWER(TRIM(node_subnode_key)) = LOWER(TRIM($1::text))
OR LOWER(TRIM(database_name)) = LOWER(TRIM($1::text))
LIMIT 1
`;
const r = await pgPool.query(sql, [nodoName]);
return r.rows[0] ?? null;
}
/**
* Asocia el nombre de archivo de respaldo (sin extensión) a una fila de `database_nodes`.
* Cubre: igualdad sin mayúsculas y prefijos tipo `NODO001_full_20240414` → nodo `NODO001`.
*/
export function matchNodeRowFromBackupStem(stem: string, nodes: any[]): any | null {
const raw = String(stem ?? '').trim();
if (!raw || !nodes?.length) return null;
const key = raw.toLowerCase();
const nodoOf = (row: any) => String(row.NodoSubNodo ?? '').trim().toLowerCase();
const bdOf = (row: any) => String(row.BDName ?? '').trim().toLowerCase();
for (const row of nodes) {
const n = nodoOf(row);
const b = bdOf(row);
if (n && n === key) return row;
if (b && b === key) return row;
}
type Cand = { row: any; len: number };
const cands: Cand[] = [];
for (const row of nodes) {
const n = nodoOf(row);
const b = bdOf(row);
if (n && (key === n || key.startsWith(`${n}_`) || key.startsWith(`${n}-`))) {
cands.push({ row, len: n.length });
}
if (b && b !== n && (key === b || key.startsWith(`${b}_`) || key.startsWith(`${b}-`))) {
cands.push({ row, len: b.length });
}
}
if (!cands.length) return null;
cands.sort((a, b) => b.len - a.len);
return cands[0].row;
}
/** Datos de contacto para alertas (equivalente a la consulta previa sobre Usuarios/BasesDeDatos). */
export async function lookupAlertClientData(nodoName: string): Promise<any | null> {
const sql = `
SELECT
pu.is_authority_client AS "ClienteAutoridad",
pu.full_name AS "Nombre",
pu.username AS "Usuario",
dn.notification_email AS "CorreoNotificacion"
FROM ${qUsers()} pu
LEFT JOIN ${qNodes()} dn ON pu.database_node_id = dn.id
WHERE pu.username = $1
OR dn.database_name = $1
OR dn.node_subnode_key = $1
LIMIT 1
`;
const r = await pgPool.query(sql, [nodoName]);
return r.rows[0] ?? null;
}
export async function updateNodeActive(id: number, activo: boolean): Promise<void> {
await pgPool.query(`UPDATE ${qNodes()} SET is_active = $1 WHERE id = $2`, [activo ? 1 : 0, id]);
}
export async function updateNodeLegalName(id: number, nombre: string): Promise<void> {
await pgPool.query(`UPDATE ${qNodes()} SET legal_name = $1 WHERE id = $2`, [nombre, id]);
}
export async function insertDatabaseNode(row: {
nodoSubNodo: string;
rfc: string;
nombre: string;
sucursal: string;
correo: string;
serverName: string;
bdName: string;
activo: number;
}): Promise<void> {
await pgPool.query(
`
INSERT INTO ${qNodes()} (
node_subnode_key, rfc, legal_name, branch_name,
notification_email, server_name, database_name, is_active
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
`,
[
row.nodoSubNodo,
row.rfc,
row.nombre,
row.sucursal,
row.correo,
row.serverName,
row.bdName,
row.activo
]
);
}
export async function updateDatabaseNode(
id: number,
row: {
nodoSubNodo: string;
rfc: string;
nombre: string;
sucursal: string;
correo: string;
serverName: string;
bdName: string;
activo: number;
}
): Promise<void> {
await pgPool.query(
`
UPDATE ${qNodes()} SET
node_subnode_key = $1,
rfc = $2,
legal_name = $3,
branch_name = $4,
notification_email = $5,
server_name = $6,
database_name = $7,
is_active = $8
WHERE id = $9
`,
[
row.nodoSubNodo,
row.rfc,
row.nombre,
row.sucursal,
row.correo,
row.serverName,
row.bdName,
row.activo,
id
]
);
}
export async function deleteDatabaseNode(id: number): Promise<void> {
await pgPool.query(`DELETE FROM ${qNodes()} WHERE id = $1`, [id]);
}
export async function insertPortalUser(row: {
databaseNodeId: number;
isAuthorityClient: number;
fullName: string;
username: string;
passwordHash: string;
bdShelter: string | null;
}): Promise<void> {
await pgPool.query(
`
INSERT INTO ${qUsers()} (
database_node_id, is_authority_client, full_name, username, password_hash, bd_shelter
) VALUES ($1, $2, $3, $4, $5, $6)
`,
[
row.databaseNodeId,
row.isAuthorityClient,
row.fullName,
row.username,
row.passwordHash,
row.bdShelter
]
);
}
export async function updatePortalUser(
id: number,
row: {
databaseNodeId: number;
isAuthorityClient: number;
fullName: string;
username: string;
bdShelter: string | null;
passwordHash?: string;
}
): Promise<void> {
if (row.passwordHash !== undefined) {
await pgPool.query(
`
UPDATE ${qUsers()} SET
database_node_id = $1,
is_authority_client = $2,
full_name = $3,
username = $4,
password_hash = $5,
bd_shelter = $6
WHERE id = $7
`,
[
row.databaseNodeId,
row.isAuthorityClient,
row.fullName,
row.username,
row.passwordHash,
row.bdShelter,
id
]
);
} else {
await pgPool.query(
`
UPDATE ${qUsers()} SET
database_node_id = $1,
is_authority_client = $2,
full_name = $3,
username = $4,
bd_shelter = $5
WHERE id = $6
`,
[row.databaseNodeId, row.isAuthorityClient, row.fullName, row.username, row.bdShelter, id]
);
}
}
export async function deletePortalUser(id: number): Promise<void> {
await pgPool.query(`DELETE FROM ${qUsers()} WHERE id = $1`, [id]);
}