1st
This commit is contained in:
+115
@@ -0,0 +1,115 @@
|
|||||||
|
import mysql from 'mysql2/promise'
|
||||||
|
import { loadP42Secrets } from './secretsLoader.js'
|
||||||
|
|
||||||
|
export class MySQLClient {
|
||||||
|
|
||||||
|
constructor(db, kal = 0) {
|
||||||
|
this.db = db
|
||||||
|
this.lastInsertedId = -1
|
||||||
|
this.kalIntervalId = null
|
||||||
|
if(kal > 0) {
|
||||||
|
this.kalIntervalId = setInterval(() => {
|
||||||
|
this.db.query('SELECT 1')
|
||||||
|
}, 1000 * kal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static #assertValidDbName(name) {
|
||||||
|
if(typeof(name) !== 'string' || !/^[A-Za-z0-9_]+$/.test(name)) {
|
||||||
|
throw(new Error(`Invalid MySQL database name: ${name}`))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static resolveDatabases(config = {}) {
|
||||||
|
loadP42Secrets()
|
||||||
|
const guiDatabase = process.env.mysql_guiDb || config.guiDatabase || config.database || 'p42GUI'
|
||||||
|
const simDatabase = process.env.mysql_simDb || config.simDatabase || 'p42SIM'
|
||||||
|
MySQLClient.#assertValidDbName(guiDatabase)
|
||||||
|
MySQLClient.#assertValidDbName(simDatabase)
|
||||||
|
return({ guiDatabase, simDatabase })
|
||||||
|
}
|
||||||
|
|
||||||
|
static resolveCredentials(config = {}) {
|
||||||
|
loadP42Secrets()
|
||||||
|
const user = process.env.mysql_user
|
||||||
|
const password = process.env.mysql_pass
|
||||||
|
if(!user || !password) {
|
||||||
|
throw(new Error('Missing MySQL credentials: set mysql_user and mysql_pass in environment'))
|
||||||
|
}
|
||||||
|
const { guiDatabase } = MySQLClient.resolveDatabases(config)
|
||||||
|
return({
|
||||||
|
socketPath: config.socketPath,
|
||||||
|
host: config.host,
|
||||||
|
port: config.port,
|
||||||
|
user,
|
||||||
|
password,
|
||||||
|
database: guiDatabase,
|
||||||
|
waitForConnections: true,
|
||||||
|
connectionLimit: config.connectionLimit ?? 5,
|
||||||
|
queueLimit: 0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
static async createPool(config) {
|
||||||
|
return(await mysql.createPool(MySQLClient.resolveCredentials(config)))
|
||||||
|
}
|
||||||
|
|
||||||
|
static async poolExecute(pool, query, values = []) {
|
||||||
|
const [rows] = await pool.execute(query, values)
|
||||||
|
return(rows)
|
||||||
|
}
|
||||||
|
|
||||||
|
async close() {
|
||||||
|
if(this.db) await this.db.end()
|
||||||
|
if(this.kalIntervalId) clearInterval(this.kalIntervalId)
|
||||||
|
this.db = null
|
||||||
|
}
|
||||||
|
|
||||||
|
escape(x) {
|
||||||
|
return(mysql.escape(x))
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute(query, values = []) {
|
||||||
|
if(!this.db) return([])
|
||||||
|
if(query.search(RegExp('(^|;) *(INSERT|REPLACE) +INTO', 'i')) > -1) this.lastInsertedId = -1
|
||||||
|
const [result] = await this.db.execute(query, values)
|
||||||
|
|
||||||
|
if('insertId' in result) {
|
||||||
|
this.lastInsertedId = result.insertId
|
||||||
|
}
|
||||||
|
return(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
async InsertObjectsStatement(table, rows) {
|
||||||
|
this.lastInsertedId = -1
|
||||||
|
if((!this.db) || (rows.length == 0)) return([])
|
||||||
|
const keys = Object.keys(rows[0]).map((key) => `\`${key}\``).join(', ')
|
||||||
|
const valuesArr = []
|
||||||
|
for(const row of rows) {
|
||||||
|
valuesArr.push(Object.values(row))
|
||||||
|
}
|
||||||
|
const query = `INSERT INTO ${table} (${keys}) VALUES ?`
|
||||||
|
const [result] = await this.db.query(query, [valuesArr])
|
||||||
|
if('insertId' in result) {
|
||||||
|
this.lastInsertedId = result.insertId
|
||||||
|
}
|
||||||
|
return(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
async ReplaceObjectsStatement(table, rows) {
|
||||||
|
this.lastInsertedId = -1
|
||||||
|
if((!this.db) || (rows.length == 0)) return([])
|
||||||
|
const keys = Object.keys(rows[0]).map((key) => `\`${key}\``).join(', ')
|
||||||
|
const valuesArr = []
|
||||||
|
for(const row of rows) {
|
||||||
|
valuesArr.push(Object.values(row))
|
||||||
|
}
|
||||||
|
const query = `REPLACE INTO ${table} (${keys}) VALUES ?`
|
||||||
|
const [result] = await this.db.query(query, [valuesArr])
|
||||||
|
if('insertId' in result) {
|
||||||
|
this.lastInsertedId = result.insertId
|
||||||
|
}
|
||||||
|
return(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"name": "p42modules",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"type": "module",
|
||||||
|
"dependencies": {
|
||||||
|
"mysql2": "^3.11.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import fs from 'fs'
|
||||||
|
|
||||||
|
const DEFAULT_SECRETS_PATH = '/etc/p42/secrets.env'
|
||||||
|
|
||||||
|
function stripQuotes(value) {
|
||||||
|
if(
|
||||||
|
(value.startsWith('"') && value.endsWith('"')) ||
|
||||||
|
(value.startsWith("'") && value.endsWith("'"))
|
||||||
|
) {
|
||||||
|
return(value.slice(1, -1))
|
||||||
|
}
|
||||||
|
return(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function loadP42Secrets(filePath = DEFAULT_SECRETS_PATH) {
|
||||||
|
if(process.env.mysql_user && process.env.mysql_pass) return(true)
|
||||||
|
|
||||||
|
if(!fs.existsSync(filePath)) return(false)
|
||||||
|
|
||||||
|
const text = fs.readFileSync(filePath, 'utf8')
|
||||||
|
for(const rawLine of text.split('\n')) {
|
||||||
|
const line = rawLine.trim()
|
||||||
|
if(!line || line.startsWith('#')) continue
|
||||||
|
const eq = line.indexOf('=')
|
||||||
|
if(eq < 1) continue
|
||||||
|
const key = line.slice(0, eq).trim()
|
||||||
|
const value = stripQuotes(line.slice(eq + 1).trim())
|
||||||
|
if(key === 'mysql_user' || key === 'mysql_pass') {
|
||||||
|
process.env[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return(Boolean(process.env.mysql_user && process.env.mysql_pass))
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user