mirror of
https://gitlab.com/MrFry/mrfrys-node-server
synced 2025-04-01 20:24:18 +02:00
351 lines
8.1 KiB
TypeScript
351 lines
8.1 KiB
TypeScript
/* ----------------------------------------------------------------------------
|
|
|
|
Question Server
|
|
GitLab: <https://gitlab.com/MrFry/mrfrys-node-server>
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
------------------------------------------------------------------------- */
|
|
|
|
// https://www.sqlitetutorial.net/sqlite-nodejs/
|
|
// https://github.com/JoshuaWise/better-sqlite3/blob/HEAD/docs/api.md
|
|
|
|
export default {
|
|
GetDB: GetDB,
|
|
AddColumn: AddColumn,
|
|
TableInfo: TableInfo,
|
|
Update: Update,
|
|
Delete: Delete,
|
|
CreateTable: CreateTable,
|
|
SelectAll: SelectAll,
|
|
Select: Select,
|
|
Insert: Insert,
|
|
CloseDB: CloseDB,
|
|
runStatement: runStatement,
|
|
sanitizeQuery: sanitizeQuery,
|
|
}
|
|
|
|
import Sqlite, { Database, RunResult } from 'better-sqlite3'
|
|
import logger from '../utils/logger'
|
|
import utils from '../utils/utils'
|
|
|
|
const debugLog = process.env.NS_SQL_DEBUG_LOG
|
|
|
|
function sanitizeQuery(val: string | number): string | number {
|
|
if (typeof val === 'string') {
|
|
return val.replace(/'/g, '').replace(/;/g, '')
|
|
}
|
|
return val
|
|
}
|
|
|
|
// { asd: 'asd', basd: 4 } => asd = 'asd', basd = 4
|
|
function GetSqlQuerry(
|
|
conditions: { [key: string]: string | number },
|
|
type?: string,
|
|
joiner?: string
|
|
) {
|
|
const res = Object.keys(conditions).reduce((acc, key) => {
|
|
const item = conditions[key]
|
|
const conditionKey = sanitizeQuery(key)
|
|
const condition = sanitizeQuery(conditions[key])
|
|
|
|
if (typeof item === 'string') {
|
|
acc.push(`${conditionKey} = '${condition}'`)
|
|
} else {
|
|
acc.push(`${conditionKey} = ${condition}`)
|
|
}
|
|
return acc
|
|
}, [])
|
|
if (type === 'where') {
|
|
if (joiner) {
|
|
return res.join(` ${joiner} `)
|
|
} else {
|
|
return res.join(' AND ')
|
|
}
|
|
} else {
|
|
return res.join(', ')
|
|
}
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
|
|
function GetDB(path: string): Database {
|
|
utils.CreatePath(path)
|
|
const res = new Sqlite(path)
|
|
res.pragma('synchronous = OFF')
|
|
return res
|
|
}
|
|
|
|
function DebugLog(msg: string) {
|
|
if (debugLog) {
|
|
logger.DebugLog(msg, 'sql', 0)
|
|
}
|
|
}
|
|
|
|
// FIXME: this might not work: what is col exactly, and how we use AddColumn?
|
|
function AddColumn(
|
|
db: Database,
|
|
table: string,
|
|
col: { [key: string]: string | number }
|
|
): RunResult {
|
|
try {
|
|
const colName = Object.keys(col)[0]
|
|
const colType = col.type
|
|
|
|
const command = `ALTER TABLE ${table} ADD COLUMN ${colName} ${colType}`
|
|
const stmt = PrepareStatement(db, command)
|
|
|
|
return stmt.run()
|
|
} catch (err) {
|
|
console.error(err)
|
|
return null
|
|
}
|
|
}
|
|
|
|
function TableInfo(
|
|
db: Database,
|
|
table: string
|
|
): {
|
|
columns: any[]
|
|
dataCount: number
|
|
} {
|
|
try {
|
|
const command = `PRAGMA table_info(${table})`
|
|
const stmt = PrepareStatement(db, command)
|
|
|
|
const infoRes = stmt.all()
|
|
|
|
const s2 = `SELECT COUNT(*) FROM ${table}`
|
|
const stmt2 = PrepareStatement(db, s2)
|
|
|
|
const countRes = stmt2.get()
|
|
|
|
return {
|
|
columns: infoRes,
|
|
dataCount: countRes[Object.keys(countRes)[0]],
|
|
}
|
|
} catch (err) {
|
|
console.error(err)
|
|
return null
|
|
}
|
|
}
|
|
|
|
function Update(
|
|
db: Database,
|
|
table: string,
|
|
newData: { [key: string]: string | number },
|
|
conditions: { [key: string]: string | number }
|
|
): RunResult {
|
|
try {
|
|
const command = `UPDATE ${table} SET ${GetSqlQuerry(
|
|
newData,
|
|
'set'
|
|
)} WHERE ${GetSqlQuerry(conditions, 'where')}`
|
|
const stmt = PrepareStatement(db, command)
|
|
|
|
return stmt.run()
|
|
} catch (err) {
|
|
console.error(err)
|
|
return null
|
|
}
|
|
}
|
|
|
|
function Delete(
|
|
db: Database,
|
|
table: string,
|
|
conditions: { [key: string]: string | number }
|
|
): RunResult {
|
|
try {
|
|
const command = `DELETE FROM ${table} WHERE ${GetSqlQuerry(
|
|
conditions,
|
|
'where'
|
|
)}`
|
|
const stmt = PrepareStatement(db, command)
|
|
|
|
return stmt.run()
|
|
} catch (err) {
|
|
console.error(err)
|
|
return null
|
|
}
|
|
}
|
|
|
|
interface DbColumnDescription {
|
|
[key: string]: {
|
|
type: string
|
|
primary?: boolean
|
|
autoIncrement?: boolean
|
|
notNull?: boolean
|
|
defaultZero?: boolean
|
|
[key: string]: any
|
|
}
|
|
}
|
|
|
|
function CreateTable(
|
|
db: Database,
|
|
name: string,
|
|
columns: DbColumnDescription,
|
|
foreignKeys: {
|
|
keysFrom: string[]
|
|
table: string
|
|
keysTo: string[]
|
|
}[]
|
|
): RunResult {
|
|
// CREATE TABLE users(pw text PRIMARY KEY NOT NULL, id number, lastIP text, notes text, loginCount
|
|
// number, lastLogin text, lastAccess text
|
|
//
|
|
// FOREIGN KEY(songartist, songalbum) REFERENCES album(albumartist, albumname) )
|
|
|
|
try {
|
|
const cols = Object.keys(columns)
|
|
.reduce((acc, key) => {
|
|
const item = columns[key]
|
|
const flags: string[] = []
|
|
const toCheck = {
|
|
primary: 'PRIMARY KEY',
|
|
notNull: 'NOT NULL',
|
|
unique: 'UNIQUE',
|
|
autoIncrement: 'AUTOINCREMENT',
|
|
defaultZero: 'DEFAULT 0',
|
|
}
|
|
Object.keys(toCheck).forEach((key) => {
|
|
if (item[key]) {
|
|
flags.push(toCheck[key])
|
|
}
|
|
})
|
|
|
|
acc.push(`${key} ${item.type} ${flags.join(' ')}`)
|
|
return acc
|
|
}, [])
|
|
.join(', ')
|
|
|
|
const fKeys: string[] = []
|
|
if (foreignKeys) {
|
|
foreignKeys.forEach((foreignKey) => {
|
|
const { keysFrom, table, keysTo } = foreignKey
|
|
fKeys.push(
|
|
`, FOREIGN KEY(${keysFrom.join(
|
|
', '
|
|
)}) REFERENCES ${table}(${keysTo.join(', ')})`
|
|
)
|
|
})
|
|
}
|
|
|
|
// IF NOT EXISTS
|
|
const command = `CREATE TABLE ${name}(${cols}${fKeys.join(' ')})`
|
|
const stmt = PrepareStatement(db, command)
|
|
return stmt.run()
|
|
} catch (err) {
|
|
console.error(err)
|
|
return null
|
|
}
|
|
}
|
|
|
|
function SelectAll(db: Database, from: string): any[] {
|
|
try {
|
|
const command = `SELECT * from ${from}`
|
|
|
|
const stmt = PrepareStatement(db, command)
|
|
return stmt.all()
|
|
} catch (err) {
|
|
console.error(err)
|
|
return null
|
|
}
|
|
}
|
|
|
|
// SELECT * FROM MyTable WHERE SomeColumn > LastValue ORDER BY SomeColumn LIMIT 100;
|
|
function Select(
|
|
db: Database,
|
|
from: string,
|
|
conditions: { [key: string]: string | number },
|
|
options: { joiner?: string; limit?: number } = {}
|
|
): any[] {
|
|
const { joiner, limit } = options
|
|
|
|
try {
|
|
let command = `SELECT * from ${from} WHERE ${GetSqlQuerry(
|
|
conditions,
|
|
'where',
|
|
joiner
|
|
)}`
|
|
|
|
if (!isNaN(limit)) {
|
|
command += ` LIMIT ${limit}`
|
|
}
|
|
|
|
const stmt = PrepareStatement(db, command)
|
|
return stmt.all()
|
|
} catch (err) {
|
|
console.error(err)
|
|
return null
|
|
}
|
|
}
|
|
|
|
function Insert(
|
|
db: Database,
|
|
table: string,
|
|
data: { [key: string]: number | string }
|
|
): RunResult {
|
|
try {
|
|
const cols = Object.keys(data)
|
|
.reduce((acc, key) => {
|
|
acc.push(`${key}`)
|
|
return acc
|
|
}, [])
|
|
.join(', ')
|
|
|
|
const values = Object.keys(data)
|
|
.map((item) => {
|
|
if (typeof item === 'string') {
|
|
return `'${item}'`
|
|
} else {
|
|
return `${item}`
|
|
}
|
|
})
|
|
.join(', ')
|
|
|
|
const command = `INSERT INTO ${table} (${cols}) VALUES (${values})`
|
|
const stmt = PrepareStatement(db, command)
|
|
|
|
return stmt.run()
|
|
} catch (err) {
|
|
console.error(err)
|
|
return null
|
|
}
|
|
}
|
|
|
|
function runStatement(db: Database, command: string, runType?: string): any {
|
|
const stmt = PrepareStatement(db, command)
|
|
if (!runType) {
|
|
return stmt.all()
|
|
} else if (runType === 'run') {
|
|
return stmt.run()
|
|
}
|
|
return null
|
|
}
|
|
|
|
function CloseDB(db: Database): void {
|
|
db.close()
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
|
|
function PrepareStatement(db: Database, command: string) {
|
|
if (!db) {
|
|
throw new Error(
|
|
'DB is undefined in prepare statement! DB action called with undefined db'
|
|
)
|
|
}
|
|
DebugLog(command)
|
|
return db.prepare(command)
|
|
}
|