commit
ce821ae088
@ -1,5 +1,5 @@
|
||||
/* template index.js. Doesn't implement actual commands */
|
||||
const cmdLib = require('../../CommandLib'); // required for command objects
|
||||
const cmdLib = require('../../lib/command'); // required for command objects
|
||||
|
||||
/**
|
||||
* A description what the command module includes and why. Doesn't need to list commands but explains
|
@ -1,6 +1,6 @@
|
||||
const cmdLib = require('../../CommandLib'),
|
||||
const cmdLib = require('../../lib/command'),
|
||||
fsx = require('fs-extra'),
|
||||
utils = require('../../utils');
|
||||
utils = require('../../lib/utils');
|
||||
|
||||
/**
|
||||
* Info commands provide information about the bot. These informations are
|
@ -1,7 +1,7 @@
|
||||
const fetch = require('node-fetch'),
|
||||
fsx = require('fs-extra'),
|
||||
yaml = require('js-yaml'),
|
||||
queryPath = './lib/api/graphql/AnilistApi',
|
||||
queryPath = __dirname + '/graphql',
|
||||
alApiEndpoint = 'https://graphql.anilist.co';
|
||||
|
||||
async function getFragments() {
|
@ -0,0 +1,175 @@
|
||||
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<any>}
|
||||
*/
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
Object.assign(exports, {
|
||||
Column: genericSql.Column,
|
||||
Database: Database
|
||||
});
|
@ -1,102 +0,0 @@
|
||||
const music = require('./MusicLib'),
|
||||
utils = require('./utils'),
|
||||
config = require('../config.json'),
|
||||
sqliteAsync = require('./sqliteAsync'),
|
||||
logging = require('./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,195 @@
|
||||
const music = require('../music'),
|
||||
dblib = require('../database'),
|
||||
logging = require('../utils/logging');
|
||||
|
||||
/**
|
||||
* 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.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the column where the key has the value keyvalue
|
||||
* @param table {String} - the table name
|
||||
* @param column {String} - the name of the column
|
||||
* @param keyname {String} - the name of the key
|
||||
* @param keyvalue {*} - the value of the key
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
async getSingleValue(table, column, keyname, keyvalue) {
|
||||
let result = await this.get(this.sql.select(table, false, column,
|
||||
this.sql.where(this.sql.parameter(1), '=', this.sql.parameter(2))),
|
||||
[keyname, keyvalue]);
|
||||
if (result)
|
||||
return result[column];
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns either the whole table or a limited version
|
||||
* @param tablename
|
||||
* @param limit
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async getTableContent(tablename, limit) {
|
||||
if (limit)
|
||||
return await this.all(this.sql.select(tablename, false, ['*'], [], [
|
||||
this.sql.limit(limit)
|
||||
]));
|
||||
else
|
||||
return await this.all(this.sql.select(tablename, false, ['*'], [], []));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
});
|
@ -0,0 +1,76 @@
|
||||
const logging = require('../utils/logging'),
|
||||
EventEmitter = require('events');
|
||||
|
||||
/**
|
||||
* Extends the event emitter with some useful features.
|
||||
*/
|
||||
class ExtendedEventEmitter extends EventEmitter {
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* @param [name] {String}
|
||||
*/
|
||||
constructor(name) {
|
||||
super();
|
||||
this._logger = new logging.Logger(`${name}-${this.constructor.name}`);
|
||||
this._registerDefault();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registeres the error event to the logger so it won't crash the bot.
|
||||
* @private
|
||||
*/
|
||||
_registerDefault() {
|
||||
this.on('error', (err) => {
|
||||
this._logger.error(err.message);
|
||||
this._logger.debug(err.stack);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an object of events with listeners to the bot.
|
||||
* @param eventListenerObject
|
||||
* @returns {ExtendedEventEmitter}
|
||||
*/
|
||||
addListeners(eventListenerObject) {
|
||||
for (let [event, listener] of Object.entries(eventListenerObject))
|
||||
this.on(event, listener);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all registered events.
|
||||
* @returns {*|Array<string | symbol>|string[]}
|
||||
*/
|
||||
get events() {
|
||||
return this.eventNames();
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper around getMaxListeners function
|
||||
* @returns {*|number}
|
||||
*/
|
||||
get maxListeners() {
|
||||
return this.getMaxListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper around setMaxListeners function.
|
||||
* @param n
|
||||
* @returns {this | this | Cluster | *}
|
||||
*/
|
||||
set maxListeners(n) {
|
||||
return this.setMaxListeners(n);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if the emitter has additional listeners apart from the error listener.
|
||||
*/
|
||||
get hasListeners() {
|
||||
return this.events.count > 1;
|
||||
}
|
||||
}
|
||||
|
||||
Object.assign(exports, {
|
||||
ExtendedEventEmitter: ExtendedEventEmitter,
|
||||
});
|
@ -0,0 +1,413 @@
|
||||
/**
|
||||
* Returns types based on the database.
|
||||
*/
|
||||
class GenericTypes {
|
||||
/**
|
||||
* Constructor.
|
||||
* @param database {String}
|
||||
*/
|
||||
constructor(database) {
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
get null() {
|
||||
switch(this.database) {
|
||||
case 'postgresql':
|
||||
case 'sqlite':
|
||||
default:
|
||||
return 'NULL';
|
||||
}
|
||||
}
|
||||
|
||||
get integer() {
|
||||
switch(this.database) {
|
||||
case 'postgresql':
|
||||
case 'sqlite':
|
||||
default:
|
||||
return 'INTEGER';
|
||||
}
|
||||
}
|
||||
|
||||
get real() {
|
||||
switch(this.database) {
|
||||
case 'sqlite':
|
||||
return 'REAL';
|
||||
case 'postgresql':
|
||||
default:
|
||||
return 'FLOAT';
|
||||
}
|
||||
}
|
||||
|
||||
get text() {
|
||||
switch (this.database) {
|
||||
case 'postgresql':
|
||||
case 'sqlite':
|
||||
default:
|
||||
return 'TEXT';
|
||||
}
|
||||
}
|
||||
|
||||
get varchar() {
|
||||
switch (this.database) {
|
||||
case 'postgresql':
|
||||
case 'sqlite':
|
||||
default:
|
||||
return 'VARCHAR';
|
||||
}
|
||||
}
|
||||
|
||||
get date() {
|
||||
switch (this.database) {
|
||||
case 'postgresql':
|
||||
case 'sqlite':
|
||||
default:
|
||||
return 'DATE';
|
||||
}
|
||||
}
|
||||
|
||||
get datetime() {
|
||||
switch (this.database) {
|
||||
case 'postgresql':
|
||||
return 'TIMESTAMP';
|
||||
case 'sqlite':
|
||||
default:
|
||||
return 'DATETIME';
|
||||
}
|
||||
}
|
||||
|
||||
get serial() {
|
||||
switch (this.database) {
|
||||
case 'sqlite':
|
||||
return 'INTEGER AUTOINCREMENT NOT NULL';
|
||||
case 'postgresql':
|
||||
default:
|
||||
return 'SERIAL';
|
||||
}
|
||||
}
|
||||
|
||||
get serialPK() {
|
||||
switch (this.database) {
|
||||
case 'sqlite':
|
||||
return 'INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE NOT NULL';
|
||||
case 'postgresql':
|
||||
default:
|
||||
return 'SERIAL PRIMARY KEY UNIQUE';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the VARCHAR type with the specified length.
|
||||
* @param length {Number}
|
||||
*/
|
||||
getVarchar(length) {
|
||||
return `${this.varchar}(${length})`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns sql statements based on the database.
|
||||
*/
|
||||
class GenericSql {
|
||||
/**
|
||||
* Constructor.
|
||||
* @param database {String}
|
||||
*/
|
||||
constructor(database) {
|
||||
this.database = database;
|
||||
this.types = new GenericTypes(database);
|
||||
this.constraints = {
|
||||
primaryKey: 'PRIMARY KEY',
|
||||
notNull: 'NOT NULL',
|
||||
unique: 'UNIQUE',
|
||||
like: 'LIKE',
|
||||
exists: 'EXISTS',
|
||||
and: 'AND',
|
||||
or: 'OR',
|
||||
in: 'IN',
|
||||
any: 'ANY',
|
||||
all: 'ALL'
|
||||
};
|
||||
this.templates = {
|
||||
idcolumn: new Column('id', this.types.serialPK, [])
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a value placeholder for the specified number.
|
||||
* @param number {Number} - the variables position.
|
||||
*/
|
||||
parameter(number) {
|
||||
switch (this.database) {
|
||||
case 'postgresql':
|
||||
return `$${number}`;
|
||||
case 'sqlite':
|
||||
return '?';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A sum selector - calculates the sum of all values of the column
|
||||
* @param colname {String} - the name of the column where the sum is selected.
|
||||
* @returns {string}
|
||||
*/
|
||||
sum(colname) {
|
||||
switch (this.database) {
|
||||
case 'postgresql':
|
||||
case 'sqlite':
|
||||
return `SUM(${colname})`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A avg selector - selects the average
|
||||
* @param colname {String} - the name of the column where the avg value is selected.
|
||||
* @returns {string}
|
||||
*/
|
||||
avg(colname) {
|
||||
switch (this.database) {
|
||||
case 'postgresql':
|
||||
case 'sqlite':
|
||||
return `AVG(${colname})`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A min selector - selects the minimum
|
||||
* @param colname {String} - the name of the column where the min value is selected.
|
||||
* @returns {string}
|
||||
*/
|
||||
min(colname) {
|
||||
switch (this.database) {
|
||||
case 'postgresql':
|
||||
case 'sqlite':
|
||||
return `MIN(${colname})`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A max selector - selects the maximum
|
||||
* @param colname {String} - the name of the column where the max value is selected.
|
||||
* @returns {string}
|
||||
*/
|
||||
max(colname) {
|
||||
switch (this.database) {
|
||||
case 'postgresql':
|
||||
case 'sqlite':
|
||||
return `MAX(${colname})`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A count selector - counts the results
|
||||
* @param colname {String} - the name of the column to be counted.
|
||||
* @returns {string}
|
||||
*/
|
||||
count(colname) {
|
||||
switch (this.database) {
|
||||
case 'postgresql':
|
||||
case 'sqlite':
|
||||
return `COUNT(${colname}) count`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A default constraint
|
||||
* @param expression {String} - the expression to generate the default value.
|
||||
* @returns {string}
|
||||
*/
|
||||
default(expression) {
|
||||
switch (this.database) {
|
||||
case 'postgresql':
|
||||
case 'sqlite':
|
||||
return `DEFAULT ${expression}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A where statement
|
||||
* @param row {String} - the row
|
||||
* @param operator {String} - the comparison operator
|
||||
* @param comparator {String} the value or row to compare to
|
||||
*/
|
||||
and(row, operator, comparator) {
|
||||
switch (this.database) {
|
||||
case 'postgresql':
|
||||
case 'sqlite':
|
||||
return `AND ${row} ${operator} ${comparator}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A or statement
|
||||
* @param row {String} - the row
|
||||
* @param operator {String} - the comparison operator
|
||||
* @param comparator {String} the value or row to compare to
|
||||
*/
|
||||
or(row, operator, comparator) {
|
||||
switch (this.database) {
|
||||
case 'postgresql':
|
||||
case 'sqlite':
|
||||
return `OR ${row} ${operator} ${comparator}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A where statement
|
||||
* @param row {String} - the row
|
||||
* @param operator {String} - the comparison operator
|
||||
* @param comparator {String} the value or row to compare to
|
||||
*/
|
||||
where(row, operator, comparator) {
|
||||
switch (this.database) {
|
||||
case 'postgresql':
|
||||
case 'sqlite':
|
||||
return `WHERE ${row} ${operator} ${comparator}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A limit statement.
|
||||
* @param count {Number} - the number of rows to return
|
||||
* @returns {string}
|
||||
*/
|
||||
limit(count) {
|
||||
switch (this.database) {
|
||||
case 'postgresql':
|
||||
case 'sqlite':
|
||||
return `LIMIT ${count}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Table statement
|
||||
* @param table {String}
|
||||
* @param rows {Array<Column>}
|
||||
* @returns {string}
|
||||
*/
|
||||
createTable(table, rows) {
|
||||
switch (this.database) {
|
||||
case 'postgresql':
|
||||
case 'sqlite':
|
||||
return `CREATE TABLE ${table} (${rows.map(x => x.sql).join(',')})`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Table if it doesn't exist statement
|
||||
* @param table {String}
|
||||
* @param columns {Array<Column>}
|
||||
* @returns {string}
|
||||
*/
|
||||
createTableIfNotExists(table, columns) {
|
||||
switch (this.database) {
|
||||
case 'postgresql':
|
||||
case 'sqlite':
|
||||
return `CREATE TABLE IF NOT EXISTS ${table} (${columns.map(x => x.sql).join(',')})`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert into the table.
|
||||
* @param table {String} - the table name
|
||||
* @param colValueObj {Object} - an object with keys as columnnames and values as columnvalues
|
||||
* @returns {string}
|
||||
*/
|
||||
insert(table, colValueObj) {
|
||||
let rownames = Object.keys(colValueObj);
|
||||
let values = Object.values(colValueObj);
|
||||
switch (this.database) {
|
||||
case 'postgresql':
|
||||
case 'sqlite':
|
||||
return `INSERT INTO ${table} (${rownames.join(',')}) values (${values.join(',')})`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the table with the rowValueObject.
|
||||
* @param table {String} - the table name
|
||||
* @param colValueObj {Object} - an object with keys as columnnames and values as columnvalues
|
||||
* @param conditions {Array<String>|String} - conditions for the update row selection (WHERE ... [OR ...][AND ...]
|
||||
* @returns {string}
|
||||
*/
|
||||
update(table, colValueObj, conditions) {
|
||||
if (!(conditions instanceof Array))
|
||||
conditions = [conditions];
|
||||
switch (this.database) {
|
||||
case 'postgresql':
|
||||
case 'sqlite':
|
||||
return `UPDATE ${table} SET ${Object.entries(colValueObj).map(x => `${x[0]} = ${x[1]}`).join(',')} ${conditions.join(' ')}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects from a table
|
||||
* @param table {String} - the tablename
|
||||
* @param distinct {String|boolean} - should distinct values be selected? If yes provide distinct keyword.
|
||||
* @param colnames {Array<String>|String} - the rows to select
|
||||
* @param conditions {Array<String>|String} - conditions for the row selection (WHERE ... [OR ...][AND ...]
|
||||
* @param operations {Array<String>|String} - operations on the selected rows
|
||||
* @returns {String}
|
||||
*/
|
||||
select(table, distinct, colnames, conditions, operations) {
|
||||
if (!(colnames instanceof Array))
|
||||
colnames = [colnames];
|
||||
if (!(conditions instanceof Array))
|
||||
conditions = [conditions];
|
||||
if (!(operations instanceof Array))
|
||||
operations = [operations];
|
||||
switch (this.database) {
|
||||
case 'postgresql':
|
||||
case 'sqlite':
|
||||
return `SELECT${distinct? ' ' + distinct : ''} ${colnames.join(', ')} FROM ${table} ${conditions.join(' ')} ${operations.join(' ')}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes from a table
|
||||
* @param table {String} - the table name
|
||||
* @param conditions {Array<String>|String} - conditions for the row selection (WHERE ... [OR ...][AND ...]
|
||||
*/
|
||||
delete(table, conditions) {
|
||||
if (!(conditions instanceof Array))
|
||||
conditions = [conditions];
|
||||
switch (this.database) {
|
||||
case 'postgresql':
|
||||
case 'sqlite':
|
||||
return `DELETE FROM ${table} ${conditions.join(' ')}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Column {
|
||||
/**
|
||||
* Create a column for usage in the generic sql statements
|
||||
* @param name {String}
|
||||
* @param [type] {String}
|
||||
* @param [constraints] {Array<String>}
|
||||
*/
|
||||
constructor(name, type, constraints) {
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
this.constraints = constraints || [];
|
||||
if (!(constraints instanceof Array))
|
||||
this.constraints = [constraints];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the datatype of the row.
|
||||
* @param constraint {String}
|
||||
*/
|
||||
addConstraint(constraint) {
|
||||
this.constraints.push(constraint);
|
||||
}
|
||||
|
||||
get sql() {
|
||||
return `${this.name} ${this.type} ${this.constraints.join(' ')}`;
|
||||
}
|
||||
}
|
||||
|
||||
Object.assign(exports, {
|
||||
GenericSql: GenericSql,
|
||||
GenericTypes: GenericTypes,
|
||||
Column: Column
|
||||
});
|
Loading…
Reference in New Issue