feat: initialize core server architecture, API key socket handler, and postgres patch utilities
All checks were successful
Deploy Beta (NATIVE) / deploy (push) Successful in 13s
All checks were successful
Deploy Beta (NATIVE) / deploy (push) Successful in 13s
This commit is contained in:
parent
766c3deae0
commit
873c295e35
9 changed files with 131 additions and 15 deletions
|
|
@ -259,16 +259,16 @@ export async function getDisableAuthSession(): ReturnType<typeof getSession> {
|
|||
createdAt: obj.createdAt as Date,
|
||||
updatedAt: obj.updatedAt as Date,
|
||||
email: obj.email as string,
|
||||
emailVerified: obj.emailVerified === 1,
|
||||
emailVerified: Boolean(obj.emailVerified),
|
||||
name: obj.name as string,
|
||||
image: obj.image as string | null,
|
||||
username: obj.username as string | null,
|
||||
displayUsername: obj.displayUsername as string | null,
|
||||
banned: obj.banned === 1,
|
||||
banned: Boolean(obj.banned),
|
||||
role: obj.role as string | null,
|
||||
banReason: obj.banReason as string | null,
|
||||
banExpires: obj.banExpires as Date | null,
|
||||
twoFactorEnabled: obj.twoFactorEnabled === 1,
|
||||
twoFactorEnabled: Boolean(obj.twoFactorEnabled),
|
||||
};
|
||||
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -23,8 +23,8 @@ async function sendNotificationList(socket) {
|
|||
|
||||
for (let bean of list) {
|
||||
let notificationObject = bean.export();
|
||||
notificationObject.isDefault = notificationObject.isDefault === 1;
|
||||
notificationObject.active = notificationObject.active === 1;
|
||||
notificationObject.isDefault = Boolean(notificationObject.isDefault);
|
||||
notificationObject.active = Boolean(notificationObject.active);
|
||||
result.push(notificationObject);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -503,6 +503,7 @@ class Database {
|
|||
CREATE OR REPLACE FUNCTION int_eq_bool(integer, boolean) RETURNS boolean STRICT IMMUTABLE LANGUAGE SQL AS $$ SELECT ($1 != 0) = $2; $$;
|
||||
CREATE OR REPLACE FUNCTION bool_ne_int(boolean, integer) RETURNS boolean STRICT IMMUTABLE LANGUAGE SQL AS $$ SELECT $1 != ($2 != 0); $$;
|
||||
CREATE OR REPLACE FUNCTION int_ne_bool(integer, boolean) RETURNS boolean STRICT IMMUTABLE LANGUAGE SQL AS $$ SELECT ($1 != 0) != $2; $$;
|
||||
CREATE OR REPLACE FUNCTION int_to_bool(integer) RETURNS boolean STRICT IMMUTABLE LANGUAGE SQL AS $$ SELECT ($1 != 0); $$;
|
||||
DO $$ BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_operator WHERE oprname = '=' AND oprleft = 'boolean'::regtype AND oprright = 'integer'::regtype) THEN
|
||||
CREATE OPERATOR = (LEFTARG = boolean, RIGHTARG = integer, PROCEDURE = bool_eq_int, COMMUTATOR = =);
|
||||
|
|
@ -516,6 +517,13 @@ class Database {
|
|||
IF NOT EXISTS (SELECT 1 FROM pg_operator WHERE oprname = '<>' AND oprleft = 'integer'::regtype AND oprright = 'boolean'::regtype) THEN
|
||||
CREATE OPERATOR <> (LEFTARG = integer, RIGHTARG = boolean, PROCEDURE = int_ne_bool, COMMUTATOR = <>);
|
||||
END IF;
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_cast WHERE castsource = 'integer'::regtype AND casttarget = 'boolean'::regtype) THEN
|
||||
BEGIN
|
||||
CREATE CAST (integer AS boolean) WITH FUNCTION int_to_bool(integer) AS IMPLICIT;
|
||||
EXCEPTION WHEN OTHERS THEN
|
||||
NULL;
|
||||
END;
|
||||
END IF;
|
||||
END $$;
|
||||
`);
|
||||
} catch (e) {
|
||||
|
|
|
|||
|
|
@ -1307,7 +1307,7 @@ class Monitor extends BeanModel {
|
|||
static async isActive(monitorID, active) {
|
||||
const parentActive = await Monitor.isParentActive(monitorID);
|
||||
|
||||
return active === 1 && parentActive;
|
||||
return Boolean(active) && parentActive;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -2038,7 +2038,7 @@ class Monitor extends BeanModel {
|
|||
}
|
||||
|
||||
const parentActive = await Monitor.isParentActive(parent.id);
|
||||
return parent.active === 1 && parentActive;
|
||||
return Boolean(parent.active) && parentActive;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ class Proxy {
|
|||
|
||||
// When proxy is default update deactivate old default proxy
|
||||
if (proxy.default) {
|
||||
await R.exec("UPDATE proxy SET `default` = 0 WHERE `default` = 1");
|
||||
await R.exec("UPDATE proxy SET `default` = FALSE WHERE `default` = TRUE");
|
||||
}
|
||||
|
||||
bean.user_id = userID;
|
||||
|
|
|
|||
|
|
@ -1290,7 +1290,7 @@ app.use(function (req, res, next) {
|
|||
|
||||
log.info("manage", `Clear Events Monitor: ${monitorID} User ID: ${socket.userID}`);
|
||||
|
||||
await R.exec("UPDATE heartbeat SET msg = ?, important = ? WHERE monitor_id = ? ", ["", "0", monitorID]);
|
||||
await R.exec("UPDATE heartbeat SET msg = ?, important = ? WHERE monitor_id = ? ", ["", false, monitorID]);
|
||||
|
||||
callback({
|
||||
ok: true,
|
||||
|
|
@ -1496,7 +1496,7 @@ async function initDatabase(testMode = false) {
|
|||
async function startMonitor(userID, monitorID) {
|
||||
log.info("manage", `Resume Monitor: ${monitorID} User ID: ${userID}`);
|
||||
|
||||
await R.exec("UPDATE monitor SET active = 1 WHERE id = ? ", [monitorID]);
|
||||
await R.exec("UPDATE monitor SET active = TRUE WHERE id = ? ", [monitorID]);
|
||||
|
||||
let monitor = await R.findOne("monitor", " id = ? ", [monitorID]);
|
||||
|
||||
|
|
@ -1527,7 +1527,7 @@ async function restartMonitor(userID, monitorID) {
|
|||
async function pauseMonitor(userID, monitorID) {
|
||||
log.info("manage", `Pause Monitor: ${monitorID} User ID: ${userID}`);
|
||||
|
||||
await R.exec("UPDATE monitor SET active = 0 WHERE id = ? ", [monitorID]);
|
||||
await R.exec("UPDATE monitor SET active = FALSE WHERE id = ? ", [monitorID]);
|
||||
|
||||
if (monitorID in server.monitorList) {
|
||||
await server.monitorList[monitorID].stop();
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ module.exports.apiKeySocketHandler = (socket) => {
|
|||
|
||||
log.debug("apikeys", `Disabled Key: ${keyID} User ID: ${socket.userID}`);
|
||||
|
||||
await R.exec("UPDATE api_key SET active = 0 WHERE id = ? ", [keyID]);
|
||||
await R.exec("UPDATE api_key SET active = FALSE WHERE id = ? ", [keyID]);
|
||||
|
||||
apicache.clear();
|
||||
|
||||
|
|
@ -123,7 +123,7 @@ module.exports.apiKeySocketHandler = (socket) => {
|
|||
|
||||
log.debug("apikeys", `Enabled Key: ${keyID} User ID: ${socket.userID}`);
|
||||
|
||||
await R.exec("UPDATE api_key SET active = 1 WHERE id = ? ", [keyID]);
|
||||
await R.exec("UPDATE api_key SET active = TRUE WHERE id = ? ", [keyID]);
|
||||
|
||||
apicache.clear();
|
||||
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ module.exports.statusPageSocketHandler = (socket) => {
|
|||
|
||||
let statusPageID = await StatusPage.slugToID(slug);
|
||||
|
||||
await R.exec("UPDATE incident SET pin = 0 WHERE pin = 1 AND status_page_id = ? ", [statusPageID]);
|
||||
await R.exec("UPDATE incident SET pin = FALSE WHERE pin = TRUE AND status_page_id = ? ", [statusPageID]);
|
||||
|
||||
callback({
|
||||
ok: true,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,38 @@
|
|||
const { R } = require("redbean-node");
|
||||
|
||||
const PG_BOOLEAN_COLUMNS = [
|
||||
"active",
|
||||
"pin",
|
||||
"default",
|
||||
"important",
|
||||
"public",
|
||||
"ignore_tls",
|
||||
"upside_down",
|
||||
"expiry_notification",
|
||||
"domain_expiry_notification",
|
||||
"save_response",
|
||||
"save_error_response",
|
||||
"published",
|
||||
"twofa_status",
|
||||
"is_default",
|
||||
"invert_keyword",
|
||||
"kafka_producer_ssl",
|
||||
"kafka_producer_allow_auto_topic_creation",
|
||||
"show_certificate_expiry",
|
||||
"gamedig_given_port_only",
|
||||
"ws_ignore_sec_websocket_accept_header",
|
||||
"ping_numeric",
|
||||
"show_only_last_heartbeat",
|
||||
"retry_only_on_status_code_failure",
|
||||
"cache_bust",
|
||||
"email_verified",
|
||||
"banned",
|
||||
"two_factor_enabled",
|
||||
"verified",
|
||||
"enabled",
|
||||
"rate_limit_enabled",
|
||||
];
|
||||
|
||||
function patchRedBeanPostgres() {
|
||||
const RedBeanNode = R.constructor;
|
||||
if (RedBeanNode.prototype._pgPatched) {
|
||||
|
|
@ -9,11 +42,46 @@ function patchRedBeanPostgres() {
|
|||
|
||||
function cleanPgSql(sql) {
|
||||
if (typeof sql === "string") {
|
||||
return sql.replace(/`([^`]+)`/g, '"$1"');
|
||||
// Replace MySQL backticks with standard quotes
|
||||
sql = sql.replace(/`([^`]+)`/g, '"$1"');
|
||||
// Replace bracket identifier escaping [col] with "col"
|
||||
sql = sql.replace(/\[([^\]]+)\]/g, '"$1"');
|
||||
|
||||
// PostgreSQL is strictly typed for booleans: convert integer assignments and comparisons
|
||||
const boolPattern = PG_BOOLEAN_COLUMNS.join("|");
|
||||
sql = sql.replace(new RegExp(`(["\`]?\\b(?:${boolPattern})\\b["\`]?)\\s*=\\s*1\\b`, "gi"), "$1 = true");
|
||||
sql = sql.replace(new RegExp(`(["\`]?\\b(?:${boolPattern})\\b["\`]?)\\s*=\\s*0\\b`, "gi"), "$1 = false");
|
||||
sql = sql.replace(new RegExp(`(["\`]?\\b(?:${boolPattern})\\b["\`]?)\\s*(?:!=|<>)\\s*1\\b`, "gi"), "$1 != true");
|
||||
sql = sql.replace(new RegExp(`(["\`]?\\b(?:${boolPattern})\\b["\`]?)\\s*(?:!=|<>)\\s*0\\b`, "gi"), "$1 != false");
|
||||
sql = sql.replace(new RegExp(`\\b1\\s*=\\s*(["\`]?\\b(?:${boolPattern})\\b["\`]?)`, "gi"), "true = $1");
|
||||
sql = sql.replace(new RegExp(`\\b0\\s*=\\s*(["\`]?\\b(?:${boolPattern})\\b["\`]?)`, "gi"), "false = $1");
|
||||
}
|
||||
return sql;
|
||||
}
|
||||
|
||||
function sanitizePgBeanBooleans(bean) {
|
||||
if (!bean) {
|
||||
return;
|
||||
}
|
||||
for (const col of PG_BOOLEAN_COLUMNS) {
|
||||
if (bean[col] === 0 || bean[col] === "0") {
|
||||
bean[col] = false;
|
||||
} else if (bean[col] === 1 || bean[col] === "1") {
|
||||
bean[col] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const origExec = RedBeanNode.prototype.exec;
|
||||
if (origExec) {
|
||||
RedBeanNode.prototype.exec = function (sql, data) {
|
||||
if (this.dbType === "pg") {
|
||||
sql = cleanPgSql(sql);
|
||||
}
|
||||
return origExec.call(this, sql, data);
|
||||
};
|
||||
}
|
||||
|
||||
const origNormalizeRawCore = RedBeanNode.prototype.normalizeRawCore;
|
||||
RedBeanNode.prototype.normalizeRawCore = function (sql, data) {
|
||||
if (this.dbType === "pg") {
|
||||
|
|
@ -59,6 +127,36 @@ function patchRedBeanPostgres() {
|
|||
}
|
||||
};
|
||||
|
||||
const origGetAll = RedBeanNode.prototype.getAll;
|
||||
if (origGetAll) {
|
||||
RedBeanNode.prototype.getAll = function (sql, data) {
|
||||
if (this.dbType === "pg") {
|
||||
sql = cleanPgSql(sql);
|
||||
}
|
||||
return origGetAll.call(this, sql, data);
|
||||
};
|
||||
}
|
||||
|
||||
const origGetCell = RedBeanNode.prototype.getCell;
|
||||
if (origGetCell) {
|
||||
RedBeanNode.prototype.getCell = function (sql, data) {
|
||||
if (this.dbType === "pg") {
|
||||
sql = cleanPgSql(sql);
|
||||
}
|
||||
return origGetCell.call(this, sql, data);
|
||||
};
|
||||
}
|
||||
|
||||
const origGetCol = RedBeanNode.prototype.getCol;
|
||||
if (origGetCol) {
|
||||
RedBeanNode.prototype.getCol = function (sql, data) {
|
||||
if (this.dbType === "pg") {
|
||||
sql = cleanPgSql(sql);
|
||||
}
|
||||
return origGetCol.call(this, sql, data);
|
||||
};
|
||||
}
|
||||
|
||||
const origFindCore = RedBeanNode.prototype.findCore;
|
||||
RedBeanNode.prototype.findCore = function (type, clause, data = []) {
|
||||
if (this.dbType === "pg") {
|
||||
|
|
@ -95,6 +193,9 @@ function patchRedBeanPostgres() {
|
|||
|
||||
const origStoreCore = RedBeanNode.prototype.storeCore;
|
||||
RedBeanNode.prototype.storeCore = async function (bean, changedFieldsOnly = true) {
|
||||
if (this.dbType === "pg") {
|
||||
sanitizePgBeanBooleans(bean);
|
||||
}
|
||||
if (this.dbType === "pg" && !bean.id) {
|
||||
this.devLog("Store PG insert", bean.beanMeta.type);
|
||||
await bean.storeTypeBeanList();
|
||||
|
|
@ -103,6 +204,13 @@ function patchRedBeanPostgres() {
|
|||
}
|
||||
let obj = bean.export(false);
|
||||
delete obj.id;
|
||||
for (const col of PG_BOOLEAN_COLUMNS) {
|
||||
if (obj[col] === 0 || obj[col] === "0") {
|
||||
obj[col] = false;
|
||||
} else if (obj[col] === 1 || obj[col] === "1") {
|
||||
obj[col] = true;
|
||||
}
|
||||
}
|
||||
let queryPromise = this.knex(bean.getType()).insert(obj).returning("id");
|
||||
this.queryLog(queryPromise);
|
||||
let result = await queryPromise;
|
||||
|
|
|
|||
Loading…
Reference in a new issue