From fbff2e3fd95e49714c9c6a1698be28073c43ae7b Mon Sep 17 00:00:00 2001 From: vickytechkey Date: Mon, 19 Oct 2026 19:55:15 +0530 Subject: [PATCH] fix(postgres): patch redbean-node for PostgreSQL dialect and bind handling, update service --- .forgejo/workflows/beta.yml | 2 +- package.json | 2 +- resourcemanagement.service | 4 +- server/database.js | 1 + server/server.js | 1 + server/utils/patch-redbean-postgres.js | 124 +++++++++++++++++++++++++ 6 files changed, 130 insertions(+), 4 deletions(-) create mode 100644 server/utils/patch-redbean-postgres.js diff --git a/.forgejo/workflows/beta.yml b/.forgejo/workflows/beta.yml index bf096a2..c59ee5f 100644 --- a/.forgejo/workflows/beta.yml +++ b/.forgejo/workflows/beta.yml @@ -48,7 +48,7 @@ jobs: PORT=3002 UPTIME_KUMA_PORT=3002 UPTIME_KUMA_DB_TYPE=postgres - DB_HOST=16.113.106.152 + DB_HOST=127.0.0.1 DB_PORT=5432 DB_USER=vignesh DB_PASSWORD=vignesh_secret_pass_2026 diff --git a/package.json b/package.json index 9927c58..09e9cf7 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "lint:prod": "node --run lint:js-prod && node --run lint:style", "dev": "concurrently -k -r \"wait-on tcp:3000 && node --run start-server-dev \" \"node --run start-frontend-dev\"", "start-frontend-dev": "cross-env NODE_ENV=development vite --host", - "start": "node --run start-server", + "start": "node --import=tsx server/server.js", "start-server": "node --import=tsx server/server.js", "start-server-dev": "cross-env NODE_ENV=development tsx server/server.js", "start-server-dev:watch": "cross-env NODE_ENV=development tsx --watch server/server.js", diff --git a/resourcemanagement.service b/resourcemanagement.service index e260902..090e8a0 100644 --- a/resourcemanagement.service +++ b/resourcemanagement.service @@ -10,12 +10,12 @@ Environment=NODE_ENV=production Environment=PORT=3002 Environment=UPTIME_KUMA_PORT=3002 Environment=UPTIME_KUMA_DB_TYPE=postgres -Environment=DB_HOST=16.113.106.152 +Environment=DB_HOST=127.0.0.1 Environment=DB_PORT=5432 Environment=DB_USER=vignesh Environment=DB_PASSWORD=vignesh_secret_pass_2026 Environment=DB_NAME=ResourceManagement -ExecStart=/usr/bin/npm start -- --port=3002 +ExecStart=/usr/bin/node --import=tsx server/server.js --port=3002 Restart=always RestartSec=10 StandardOutput=append:/home/ubuntu/resourcemanagement/logs/app.log diff --git a/server/database.js b/server/database.js index df3cf77..780aee5 100644 --- a/server/database.js +++ b/server/database.js @@ -1,6 +1,7 @@ const fs = require("fs"); const fsAsync = fs.promises; const { R } = require("redbean-node"); +require("./utils/patch-redbean-postgres"); const { log, sleep, isDevEnv } = require("../src/util"); const knex = require("knex"); const path = require("path"); diff --git a/server/server.js b/server/server.js index 7bd5086..2babd90 100644 --- a/server/server.js +++ b/server/server.js @@ -105,6 +105,7 @@ const express = require("express"); const expressStaticGzip = require("express-static-gzip"); log.debug("server", "Importing redbean-node"); const { R } = require("redbean-node"); +require("./utils/patch-redbean-postgres"); log.debug("server", "Importing http-graceful-shutdown"); const gracefulShutdown = require("http-graceful-shutdown"); log.debug("server", "Importing prometheus-api-metrics"); diff --git a/server/utils/patch-redbean-postgres.js b/server/utils/patch-redbean-postgres.js new file mode 100644 index 0000000..97c1455 --- /dev/null +++ b/server/utils/patch-redbean-postgres.js @@ -0,0 +1,124 @@ +const { R } = require("redbean-node"); + +function patchRedBeanPostgres() { + const RedBeanNode = R.constructor; + if (RedBeanNode.prototype._pgPatched) { + return; + } + RedBeanNode.prototype._pgPatched = true; + + function cleanPgSql(sql) { + if (typeof sql === "string") { + return sql.replace(/`([^`]+)`/g, '"$1"'); + } + return sql; + } + + const origNormalizeRawCore = RedBeanNode.prototype.normalizeRawCore; + RedBeanNode.prototype.normalizeRawCore = function (sql, data) { + if (this.dbType === "pg") { + sql = cleanPgSql(sql); + } + return origNormalizeRawCore.call(this, sql, data); + }; + + const origNormalizeRaw = RedBeanNode.prototype.normalizeRaw; + RedBeanNode.prototype.normalizeRaw = async function (sql, data) { + let result = await this.normalizeRawCore(sql, data); + this.queryLog(sql); + if (this.dbType === "mysql") { + result = result[0]; + } else if (this.dbType === "pg" && result && result.rows) { + result = result.rows; + } + return result; + }; + + RedBeanNode.prototype.getRow = async function (sql, data = [], autoLimit = false) { + if (autoLimit) { + if (this.dbType === "mssql") { + if (sql.trim().toLowerCase().startsWith("select ")) { + sql = sql.replace(/select/i, "$& TOP 1"); + } + } else if (this.dbType === "pg") { + let limitTemplate = this.knex.limit(1).toSQL(); + sql = cleanPgSql(sql) + limitTemplate.sql.replace("select *", ""); + data = data.concat(limitTemplate.bindings); + } else { + let limitTemplate = this.knex.limit(1).toSQL().toNative(); + sql = sql + limitTemplate.sql.replace("select *", ""); + data = data.concat(limitTemplate.bindings); + } + } + this.queryLog(sql); + let result = await this.normalizeRaw(sql, data); + if (result && result.length > 0) { + return result[0]; + } else { + return null; + } + }; + + const origFindCore = RedBeanNode.prototype.findCore; + RedBeanNode.prototype.findCore = function (type, clause, data = []) { + if (this.dbType === "pg") { + clause = cleanPgSql(clause); + } + return origFindCore.call(this, type, clause, data); + }; + + RedBeanNode.prototype.findOne = async function (type, clause = "", data = []) { + if (this.dbType === "pg") { + clause = cleanPgSql(clause); + } + let queryPromise = this.knex.table(type).whereRaw(clause, data).first(); + this.queryLog(queryPromise); + let obj; + try { + obj = await queryPromise; + } catch (error) { + this.checkAllowedError(error); + } + if (!obj) { + return null; + } + return this.convertToBean(type, obj); + }; + + const origCount = RedBeanNode.prototype.count; + RedBeanNode.prototype.count = async function (type, clause = "", data = [], autoLimit = true) { + if (this.dbType === "pg") { + clause = cleanPgSql(clause); + } + return origCount.call(this, type, clause, data, autoLimit); + }; + + const origStoreCore = RedBeanNode.prototype.storeCore; + RedBeanNode.prototype.storeCore = async function (bean, changedFieldsOnly = true) { + if (this.dbType === "pg" && !bean.id) { + this.devLog("Store PG insert", bean.beanMeta.type); + await bean.storeTypeBeanList(); + if (typeof bean.onUpdate === "function") { + bean.onUpdate(); + } + let obj = bean.export(false); + delete obj.id; + let queryPromise = this.knex(bean.getType()).insert(obj).returning("id"); + this.queryLog(queryPromise); + let result = await queryPromise; + bean.id = result && result[0] ? (typeof result[0] === "object" ? result[0].id : result[0]) : null; + await bean.storeSharedList(); + await bean.storeOwnList(); + bean.beanMeta.old = {}; + if (typeof bean.onAfterUpdate === "function") { + bean.onAfterUpdate(); + } + return bean.id; + } + return origStoreCore.call(this, bean, changedFieldsOnly); + }; +} + +patchRedBeanPostgres(); + +module.exports = { patchRedBeanPostgres };