Generic Database Connection
- you can now select one type of database either sqlite or postgres in the config.json (see README) - fixed bugs in generic SQL Statements - added generic database class - changed all sql scripts to generic type - added settings table to guild to manage settings of the guild - added extended database class for the guild with predefined sql statements - added music command volume to change the volume (role dj) - added music command quality to change the musics quality (role owner)feature/api-rewrite
parent
1b08edd278
commit
ec5ed87c49
@ -0,0 +1,177 @@
|
||||
const genericSql = require('../utils/genericSql'),
|
||||
logging = require('../utils/logging'),
|
||||
config = require('../../config.json');
|
||||
|
||||
class Database {
|
||||
/**
|
||||
* Creates a new database.
|
||||
* @param name {String} - the name of the database.
|
||||
*/
|
||||
constructor(name) {
|
||||
this.name = name;
|
||||
this._logger = new logging.Logger(`Database@${name}`);
|
||||
this._dbType = config.database? config.database : 'sqlite';
|
||||
if (this._dbType === 'sqlite')
|
||||
this.database = new (require('../utils/sqliteAsync')).Database(`./data/${this.name}.db`);
|
||||
else if (this._dbType === 'postgresql')
|
||||
this.database = new (require('pg')).Pool({
|
||||
user: config.databaseConnection.user,
|
||||
host: config.databaseConnection.host,
|
||||
database: config.databaseConnection.database,
|
||||
password: config.databaseConnection.password,
|
||||
port: config.databaseConnection.port
|
||||
});
|
||||
this.sql = new genericSql.GenericSql(this._dbType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the database.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async initDatabase() {
|
||||
if (this._dbType === 'sqlite') {
|
||||
await this.database.init();
|
||||
} else if (this._dbType === 'postgresql') {
|
||||
await this.database.connect();
|
||||
await this.begin();
|
||||
await this.database.query(`CREATE SCHEMA IF NOT EXISTS ${this.name.replace(/\W/g, '')}`);
|
||||
await this.database.query(`SET search_path TO ${this.name.replace(/\W/g, '')}`);
|
||||
await this.commit();
|
||||
}
|
||||
this._logger.verbose(`Connected to ${this._dbType} database ${this.name}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a sql statement with seperate values and no return.
|
||||
* Autocommit.
|
||||
* @param sql {String}
|
||||
* @param [values] {Array<String|Number>}
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
async run(sql, values) {
|
||||
this._logger.debug(`Running SQL "${sql}" with values ${values}`);
|
||||
if (this._dbType === 'sqlite')
|
||||
await this.database.run(sql, values);
|
||||
else if (this._dbType === 'postgresql')
|
||||
try {
|
||||
await this.begin();
|
||||
await this.database.query(sql, values);
|
||||
await this.commit();
|
||||
} catch (err) {
|
||||
this._logger.error(err.message);
|
||||
this._logger.verbose(err.stack);
|
||||
await this.rollback();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Begin. Part of Postgresqls BEGIN / COMMIT / ROLLBACK
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async begin() {
|
||||
if (this._dbType === 'postgresql') {
|
||||
await this.database.query('BEGIN');
|
||||
await this.database.query(`SET search_path TO ${this.name.replace(/\W/g, '')};`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a query to the current changes. No autocommit (except on sqlite).
|
||||
* @param sql
|
||||
* @param values
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async query(sql, values) {
|
||||
if (this._dbType === 'sqlite') {
|
||||
await this.run(sql, values);
|
||||
} else if (this._dbType === 'postgresql') {
|
||||
await this.database.query(sql, values);
|
||||
this._logger.debug(`Running SQL "${sql}" with values ${values}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Commit. Part of Postgresqls BEGIN / COMMIT / ROLLBACK.
|
||||
* Writes data to the database, ROLLBACK on error. (has no effect on sqlite)
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async commit() {
|
||||
if (this._dbType === 'postgresql')
|
||||
try {
|
||||
await this.database.query('COMMIT');
|
||||
} catch (err) {
|
||||
await this.database.query('ROLLBACK');
|
||||
this._logger.error(err.message);
|
||||
this._logger.verbose(err.stack);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rollback. Part of Postgresqls BEGIN / COMMIT / ROLLBACK.
|
||||
* Reverts changes done in the current commit. (has no effect on sqlite)
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async rollback() {
|
||||
if (this._dbType === 'postgresql')
|
||||
this.database.query('ROLLBACK');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Run a sql statement with seperate values and first result row as return.
|
||||
* @param sql {String} - the sql statement with escaped values ($1, $2... for postgres, ? for sqlite)
|
||||
* @param [values] {Array<String|Number>}
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async get(sql, values) {
|
||||
this._logger.debug(`Running SQL "${sql}" with values ${values}`);
|
||||
let result = null;
|
||||
if (this._dbType === 'sqlite') {
|
||||
result = await this.database.get(sql, values);
|
||||
} else if (this._dbType === 'postgresql') {
|
||||
await this.database.query(`SET search_path TO ${this.name.replace(/\W/g, '')};`);
|
||||
result = (await this.database.query({
|
||||
text: sql,
|
||||
values: values
|
||||
})).rows;
|
||||
}
|
||||
if (result instanceof Array && result.length > 0)
|
||||
return result[0];
|
||||
else
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a sql statement with seperate values and all result rows as return.
|
||||
* @param sql {String} - the sql statement with escaped values ($1, $2... for postgres, ? for sqlite)
|
||||
* @param [values] {Array<String|Number>} - the seperate values
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async all(sql, values) {
|
||||
this._logger.debug(`Running SQL "${sql}" with values ${values}`);
|
||||
if (this._dbType === 'sqlite') {
|
||||
return await this.database.all(sql, values);
|
||||
} else if (this._dbType === 'postgresql') {
|
||||
await this.database.query(`SET search_path TO ${this.name.replace(/\W/g, '')};`);
|
||||
return (await this.database.query({
|
||||
text: sql,
|
||||
values: values
|
||||
})).rows;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the connection to the database.
|
||||
*/
|
||||
close() {
|
||||
if (this._dbType === 'sqlite')
|
||||
this.database.close();
|
||||
else if (this._dbType === 'postgresql')
|
||||
this.database.release();
|
||||
}
|
||||
}
|
||||
|
||||
Object.assign(exports, {
|
||||
Column: genericSql.Column,
|
||||
Database: Database
|
||||
});
|
@ -1,102 +0,0 @@
|
||||
const music = require('./music'),
|
||||
utils = require('./utils'),
|
||||
config = require('../config.json'),
|
||||
sqliteAsync = require('./utils/sqliteAsync'),
|
||||
logging = require('./utils/logging'),
|
||||
fs = require('fs-extra'),
|
||||
dataDir = config.dataPath || './data';
|
||||
|
||||
/**
|
||||
* The Guild Handler handles guild settings and data.
|
||||
* @type {GuildHandler}
|
||||
*/
|
||||
class GuildHandler {
|
||||
|
||||
constructor(guild) {
|
||||
this.guild = guild;
|
||||
this._logger = new logging.Logger(`${this.constructor.name}@${this.guild}`);
|
||||
this.musicPlayer = new music.MusicPlayer(null);
|
||||
this._logger.silly('Initialized Guild Handler');
|
||||
this._votes = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the database
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async initDatabase() {
|
||||
this._logger.silly('Initializing Database');
|
||||
await fs.ensureDir(dataDir + '/gdb');
|
||||
this.db = new sqliteAsync.Database(`${dataDir}/gdb/${this.guild}.db`);
|
||||
await this.db.init();
|
||||
this._logger.debug(`Connected to the database for ${this.guild}`);
|
||||
this._logger.debug('Creating Databases');
|
||||
await this._createTables();
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys the guild handler
|
||||
*/
|
||||
destroy() {
|
||||
this._logger.debug('Ending musicPlayer');
|
||||
this.musicPlayer.stop();
|
||||
this._logger.debug('Ending Database');
|
||||
this.db.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates all tables needed in the Database.
|
||||
* These are at the moment:
|
||||
* messages - logs all messages send on the server
|
||||
* playlists - save playlists to play them later
|
||||
*/
|
||||
async _createTables() {
|
||||
await this.db.run(`${utils.sql.tableExistCreate} messages (
|
||||
${utils.sql.pkIdSerial},
|
||||
creation_timestamp DATETIME NOT NULL,
|
||||
author VARCHAR(128) NOT NULL,
|
||||
author_name VARCHAR(128),
|
||||
content TEXT NOT NULL
|
||||
)`);
|
||||
this._logger.silly('Created Table messages');
|
||||
await this.db.run(`${utils.sql.tableExistCreate} playlists (
|
||||
${utils.sql.pkIdSerial},
|
||||
name VARCHAR(32) UNIQUE NOT NULL,
|
||||
url VARCHAR(255) NOT NULL
|
||||
)`);
|
||||
this._logger.silly('Created Table playlists');
|
||||
await this.db.run(`${utils.sql.tableExistCreate} commands (
|
||||
${utils.sql.pkIdSerial},
|
||||
name VARCHAR(32) UNIQUE NOT NULL,
|
||||
command VARCHAR(255) NOT NULL
|
||||
)`);
|
||||
this._logger.silly('Created Table commands');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the vote counter for a command up and adds the user.
|
||||
* @param command {String}
|
||||
* @param user {String}
|
||||
*/
|
||||
updateCommandVote(command, user) {
|
||||
if (!this._votes[command])
|
||||
this._votes[command] = {count: 0, users: []};
|
||||
if (!this._votes[command].users.includes(user)) {
|
||||
this._votes[command].count++;
|
||||
this._votes[command].users.push(user);
|
||||
}
|
||||
return this._votes[command];
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the vote counter and voted users for a command.
|
||||
* @param command {String}
|
||||
*/
|
||||
resetCommandVote(command) {
|
||||
this._votes[command] = {count: 0, users: []};
|
||||
}
|
||||
}
|
||||
|
||||
Object.assign(exports, {
|
||||
GuildHandler: GuildHandler
|
||||
});
|
@ -0,0 +1,166 @@
|
||||
const music = require('../music'),
|
||||
utils = require('../utils'),
|
||||
config = require('../../config.json'),
|
||||
dblib = require('../database'),
|
||||
logging = require('../utils/logging'),
|
||||
fs = require('fs-extra'),
|
||||
dataDir = config.dataPath || './data';
|
||||
|
||||
/**
|
||||
* GuildDatabase class has abstraction for some sql statements.
|
||||
*/
|
||||
class GuildDatabase extends dblib.Database {
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* @param name
|
||||
*/
|
||||
constructor(name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates all tables needed in the guilds Database.
|
||||
*/
|
||||
async createTables() {
|
||||
let sql = this.sql;
|
||||
await this.run(sql.createTableIfNotExists('playlists', [
|
||||
sql.templates.idcolumn,
|
||||
new dblib.Column('name', sql.types.getVarchar(32), [sql.constraints.unique, sql.constraints.notNull]),
|
||||
new dblib.Column('url', sql.types.getVarchar(255), [sql.constraints.notNull])
|
||||
]));
|
||||
this._logger.silly('Created Table playlists.');
|
||||
await this.run(sql.createTableIfNotExists('commands', [
|
||||
sql.templates.idcolumn,
|
||||
new dblib.Column('name', sql.types.getVarchar(32), [sql.constraints.unique, sql.constraints.notNull]),
|
||||
new dblib.Column('command', sql.types.getVarchar(255), [sql.constraints.notNull])
|
||||
]));
|
||||
this._logger.silly('Created Table commands.');
|
||||
await this.run(sql.createTableIfNotExists('settings', [
|
||||
sql.templates.idcolumn,
|
||||
new dblib.Column('key', sql.types.getVarchar(32), [sql.constraints.unique, sql.constraints.notNull]),
|
||||
new dblib.Column('value', sql.types.getVarchar(32), [])
|
||||
]));
|
||||
this._logger.silly('Created Table settings.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of a setting
|
||||
* @param name
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
async getSetting(name) {
|
||||
let result = await this.get(this.sql.select('settings', false, 'value',
|
||||
this.sql.where('key', '=', this.sql.parameter(1))), [name]);
|
||||
if (result)
|
||||
return result.value;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all settings as object.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async getSettings() {
|
||||
let rows = await this.all(this.sql.select('settings', false, ['key', 'value'], [], []));
|
||||
let retObj = {};
|
||||
if (rows)
|
||||
for (let row of rows)
|
||||
retObj[row.key] = row.value;
|
||||
return retObj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert or update a setting parameter in the settings database.
|
||||
* @param name
|
||||
* @param value
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async setSetting(name, value) {
|
||||
let row = await this.get(this.sql.select('settings', false, [this.sql.count('*')],
|
||||
this.sql.where('key', '=', this.sql.parameter(1))), [name]);
|
||||
if (!row || Number(row.count) === 0)
|
||||
await this.run(this.sql.insert('settings', {key: this.sql.parameter(1), value: this.sql.parameter(2)}),
|
||||
[name, value]);
|
||||
else
|
||||
await this.run(this.sql.update('settings', {value: this.sql.parameter(1)},
|
||||
this.sql.where('key', '=', this.sql.parameter(2))), [value, name]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The Guild Handler handles guild settings and data.
|
||||
* @type {GuildHandler}
|
||||
*/
|
||||
class GuildHandler {
|
||||
|
||||
constructor(guild) {
|
||||
this.guild = guild;
|
||||
this._logger = new logging.Logger(`${this.constructor.name}@${this.guild}`);
|
||||
this.musicPlayer = new music.MusicPlayer(null);
|
||||
this._logger.silly('Initialized Guild Handler');
|
||||
this._votes = {};
|
||||
this.settings = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the database
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async initDatabase() {
|
||||
this._logger.silly('Initializing Database');
|
||||
this.db = new GuildDatabase(`guild_${this.guild.name.replace(/\s/g, '_').replace(/\W/g, '')}`);
|
||||
await this.db.initDatabase();
|
||||
this._logger.debug(`Connected to the database for ${this.guild}`);
|
||||
this._logger.debug('Creating Databases');
|
||||
await this.db.createTables();
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies all relevant guild settings.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async applySettings() {
|
||||
this.settings = await this.db.getSettings();
|
||||
this.musicPlayer.setVolume(Number(this.settings.musicPlayerVolume) || 0.5);
|
||||
this.musicPlayer.quality = this.settings.musicPlayerQuality || 'lowest';
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys the guild handler
|
||||
*/
|
||||
destroy() {
|
||||
this._logger.debug('Ending musicPlayer');
|
||||
this.musicPlayer.stop();
|
||||
this._logger.debug('Ending Database');
|
||||
this.db.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the vote counter for a command up and adds the user.
|
||||
* @param command {String}
|
||||
* @param user {String}
|
||||
*/
|
||||
updateCommandVote(command, user) {
|
||||
if (!this._votes[command])
|
||||
this._votes[command] = {count: 0, users: []};
|
||||
if (!this._votes[command].users.includes(user)) {
|
||||
this._votes[command].count++;
|
||||
this._votes[command].users.push(user);
|
||||
}
|
||||
return this._votes[command];
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the vote counter and voted users for a command.
|
||||
* @param command {String}
|
||||
*/
|
||||
resetCommandVote(command) {
|
||||
this._votes[command] = {count: 0, users: []};
|
||||
}
|
||||
}
|
||||
|
||||
Object.assign(exports, {
|
||||
GuildHandler: GuildHandler
|
||||
});
|
Loading…
Reference in New Issue