Storage
This commit is contained in:
parent
9f7ccc678f
commit
722fd080cf
12
options.json
12
options.json
@ -24,7 +24,17 @@
|
||||
},
|
||||
"storage": {
|
||||
"mongodb": {
|
||||
"database": "galactic"
|
||||
"database": "galactic",
|
||||
"tables": [
|
||||
"infractions",
|
||||
"guilds",
|
||||
"messages",
|
||||
"attachments",
|
||||
"users",
|
||||
"permissions",
|
||||
"role_cache",
|
||||
"webhooks"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@ const { Client } = require('discord.js');
|
||||
|
||||
const { Logger, Intercom, EventHooker, LocaleLoader, Registry, Dispatcher, Resolver } = require('./client/');
|
||||
const { Observer, Command } = require('./interfaces/');
|
||||
const StorageManager = require('./storage/StorageManager.js');
|
||||
|
||||
const options = require('../../options.json');
|
||||
|
||||
@ -18,6 +19,7 @@ class DiscordClient extends Client {
|
||||
this.intercom = new Intercom(this);
|
||||
this.logger = new Logger(this);
|
||||
this.localeLoader = new LocaleLoader(this);
|
||||
this.storageManager = new StorageManager(this, options.storage);
|
||||
|
||||
this.registry = new Registry(this);
|
||||
this.dispatcher = new Dispatcher(this);
|
||||
|
65
src/structure/storage/StorageManager.js
Normal file
65
src/structure/storage/StorageManager.js
Normal file
@ -0,0 +1,65 @@
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const chalk = require('chalk');
|
||||
|
||||
const { Provider } = require('./interfaces/');
|
||||
|
||||
class StorageManager {
|
||||
|
||||
constructor(client, options = {}) {
|
||||
|
||||
this.client = client;
|
||||
|
||||
this.providers = {};
|
||||
this.options = options;
|
||||
|
||||
}
|
||||
|
||||
async initialize() {
|
||||
|
||||
// this.manager.logger.write('debug', "Initializing storage providers.");
|
||||
|
||||
const _providers = path.join(process.cwd(), "structure/storage/providers");
|
||||
const providers = fs.readdirSync(_providers);
|
||||
|
||||
for (const _provider of providers) {
|
||||
|
||||
let provider = require(path.join(_providers, _provider));
|
||||
provider = new provider(this.client, this.options);
|
||||
|
||||
this.providers[provider.name] = provider;
|
||||
await provider.initialize();
|
||||
this._log(`Provider ${chalk.bold(provider.name)} was ${chalk.bold('loaded')}.`, provider);
|
||||
|
||||
await provider.loadTables();
|
||||
|
||||
}
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
_getName(instance) {
|
||||
if (instance instanceof Provider) return instance.name.substring(0, 5);
|
||||
return `${instance.provider.name.substring(0, 5)}:${instance.name}`;
|
||||
}
|
||||
|
||||
_error(info, instance = null) {
|
||||
this.client.logger.error(`${chalk.bold(`[STORA]`)} ${instance ? `(${this._getName(instance)}) ` : ''}${info}`);
|
||||
}
|
||||
|
||||
_log(info, instance = null) {
|
||||
this.client.logger.info(`${chalk.bold(`[STORA]`)} ${instance ? `(${this._getName(instance)}) ` : ''}${info}`);
|
||||
}
|
||||
|
||||
get mongodb() {
|
||||
return this.providers.mongodb;
|
||||
}
|
||||
|
||||
get mariadb() {
|
||||
return this.providers.mariadb;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = StorageManager;
|
14
src/structure/storage/interfaces/MariadbTable.js
Normal file
14
src/structure/storage/interfaces/MariadbTable.js
Normal file
@ -0,0 +1,14 @@
|
||||
class MariadbTable {
|
||||
|
||||
constructor(client, provider, opts = {}) {
|
||||
|
||||
this.client = client;
|
||||
this.provider = provider;
|
||||
|
||||
this.name = opts.name;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = MariadbTable;
|
191
src/structure/storage/interfaces/MongodbTable.js
Normal file
191
src/structure/storage/interfaces/MongodbTable.js
Normal file
@ -0,0 +1,191 @@
|
||||
const { ObjectId } = require('mongodb');
|
||||
|
||||
class MongodbTable {
|
||||
|
||||
constructor(client, provider, opts = {}) {
|
||||
|
||||
this.client = client;
|
||||
this.provider = provider;
|
||||
|
||||
this.name = opts.name;
|
||||
|
||||
}
|
||||
|
||||
//Data Search
|
||||
|
||||
find(query, opts = {}) { //opts: { projection: ... }
|
||||
query = this._handleData(query);
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.provider._initialized) return reject(new Error('MongoDB is not connected.'));
|
||||
this.collection.find(query, opts, async (error, cursor) => {
|
||||
if (error) return reject(error);
|
||||
return resolve(await cursor.toArray());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
findOne(query, opts = {}) { //opts: { projection: ..., sort: ... }
|
||||
query = this._handleData(query);
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.provider._initialized) return reject(new Error('MongoDB is not connected.'));
|
||||
this.collection.findOne(query, opts, async (error, item) => {
|
||||
if (error) return reject(error);
|
||||
return resolve(item);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
aggregate(query) {
|
||||
query = this._handleData(query);
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.provider._initialized) return reject(new Error('MongoDB is not connected.'));
|
||||
this.collection.aggregate(query, (error, item) => {
|
||||
if (error) return reject(error);
|
||||
return resolve(item.toArray());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
random(query, amount = 1) {
|
||||
query = this._handleData(query);
|
||||
if (amount > 100) amount = 100;
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.provider._initialized) return reject(new Error('MongoDB is not connected.'));
|
||||
this.collection.aggregate([{ $match: query }, { $sample: { size: amount } }], (error, item) => {
|
||||
if (error) return reject(error);
|
||||
return resolve(item);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
//Data Manipulation
|
||||
|
||||
insertOne(data) {
|
||||
data = this._handleData(data);
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.provider._initialized) return reject(new Error('MongoDB is not connected.'));
|
||||
this.collection.insertOne(data, (error, result) => {
|
||||
if (error) return reject(error);
|
||||
return resolve(result);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
//NOTE: insertMany?
|
||||
|
||||
deleteOne(query) {
|
||||
query = this._handleData(query);
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.provider._initialized) return reject(new Error('MongoDB is not connected.'));
|
||||
this.collection.deleteOne(query, (error, result) => {
|
||||
if (error) return reject(error);
|
||||
return resolve(result);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
deleteMany(query) {
|
||||
query = this._handleData(query);
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.provider._initialized) return reject(new Error('MongoDB is not connected.'));
|
||||
this.collection.deleteMany(query, (error, result) => {
|
||||
if (error) return reject(error);
|
||||
return resolve(result);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
updateOne(query, data, upsert = true) {
|
||||
query = this._handleData(query);
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.provider._initialized) return reject(new Error('MongoDB is not connected.'));
|
||||
this.collection.updateOne(query, { $set: data }, { upsert }, async (error, result) => {
|
||||
if (error) return reject(error);
|
||||
|
||||
const { matchedCount, upsertedCount, modifiedCount } = result;
|
||||
return resolve({ matched: matchedCount, upserted: upsertedCount, modified: modifiedCount });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
removeProperty(query, data) {
|
||||
query = this._handleData(query);
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.provider._initialized) return reject(new Error('MongoDB is not connected.'));
|
||||
|
||||
const unset = {};
|
||||
for (const field of data) unset[field] = '';
|
||||
this.collection.updateOne(query, { $unset: unset }, async (error, result) => {
|
||||
if (error) return reject(error);
|
||||
const { matchedCount, modifiedCount } = result;
|
||||
return resolve({ matched: matchedCount, modified: modifiedCount });
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
push(query, data, upsert = true) {
|
||||
query = this._handleData(query);
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.provider._initialized) return reject(new Error('MongoDB is not connected.'));
|
||||
this.collection.updateOne(query, { $push: data }, { upsert }, async (error, result) => {
|
||||
if (error) return reject(error);
|
||||
return resolve(result);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
//Statistics
|
||||
stats(options = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.provider._initialized) return reject(new Error('MongoDB is not connected.'));
|
||||
this.collection.stats(options, (error, statistics) => {
|
||||
if (error) return reject(error);
|
||||
const { ns, size, count, avgObjSize, freeStorageSize, capped } = statistics;
|
||||
return resolve({
|
||||
index: ns,
|
||||
averageSize: avgObjSize,
|
||||
currentSize: size,
|
||||
remainingSize: freeStorageSize,
|
||||
maximumSize: size + freeStorageSize,
|
||||
count,
|
||||
capped
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
count(query, options = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.provider._initialized) return reject(new Error('MongoDB is not connected.'));
|
||||
this.collection.countDocuments(query, options, (error, result) => {
|
||||
if (error) return reject(error);
|
||||
return resolve(result);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
//Lazy Function
|
||||
_handleData(data) { //Convert data._id to Mongo ObjectIds (gets converted to plaintext through shard communication)
|
||||
if (data._id) {
|
||||
if (typeof data._id === 'string') data._id = ObjectId(data._id);
|
||||
else if (typeof data._id === 'object') data._id = {
|
||||
$in: Object.values(data._id)[0].map((id) => {
|
||||
return ObjectId(id);
|
||||
})
|
||||
};
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
//Getters
|
||||
|
||||
get collection() {
|
||||
return this.provider.db.collection(this.name);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
module.exports = MongodbTable;
|
93
src/structure/storage/interfaces/Provider.js
Normal file
93
src/structure/storage/interfaces/Provider.js
Normal file
@ -0,0 +1,93 @@
|
||||
const path = require('path');
|
||||
const chalk = require('chalk');
|
||||
|
||||
const Util = require('../../../Util.js');
|
||||
|
||||
const MongodbTable = require('./MongodbTable.js');
|
||||
const MariadbTable = require('./MariadbTable.js');
|
||||
|
||||
const Constants = {
|
||||
Tables: {
|
||||
'mongodb': MongodbTable,
|
||||
'mariadb': MariadbTable
|
||||
}
|
||||
};
|
||||
|
||||
class Provider {
|
||||
|
||||
constructor(client, opts) {
|
||||
|
||||
if (!opts.config) throw new Error('No config file provided!');
|
||||
this.config = opts.config[opts.name];
|
||||
if (this.config && (!this.config.database || !this.config.host)) throw new Error('Invalid config file provided!' + JSON.stringify(this.config));
|
||||
|
||||
this.client = client;
|
||||
this.storageManager = client.storageManager;
|
||||
|
||||
this.name = opts.name;
|
||||
this.connection = null;
|
||||
this.db = null;
|
||||
|
||||
this.tables = {};
|
||||
|
||||
this._initialized = false;
|
||||
this._class = Constants.Tables[opts.name];
|
||||
|
||||
}
|
||||
|
||||
async loadTables() {
|
||||
|
||||
const directory = path.join(process.cwd(), 'structure/storage/');
|
||||
const files = await Util.readdirRecursive(path.join(directory, `tables/${this.name}`));
|
||||
|
||||
const loaded = [];
|
||||
|
||||
for (const file of files) {
|
||||
const func = require(file);
|
||||
if (typeof func !== 'function') {
|
||||
this.storageManager._error("Attempted to index an invalid function as a table.", this);
|
||||
delete require.cache[file];
|
||||
continue;
|
||||
}
|
||||
const table = new func(this.client, this);
|
||||
if (this._class && !(table instanceof this._class)) {
|
||||
this.storageManager._error("Attempted to load an invalid class as a table.", this);
|
||||
delete require.cache[file];
|
||||
continue;
|
||||
}
|
||||
|
||||
loaded.push(await this.loadTable(table));
|
||||
|
||||
}
|
||||
|
||||
for (const table of this.config.tables) {
|
||||
if (this.tables[table]) continue;
|
||||
else loaded.push(await this.loadTable(new this._class(this.client, this, {
|
||||
name: table
|
||||
})));
|
||||
}
|
||||
|
||||
return loaded;
|
||||
|
||||
}
|
||||
|
||||
async loadTable(table) {
|
||||
|
||||
if (this.tables[table.name]) {
|
||||
this.storageManager._error("Attempted to load an existing table.", table);
|
||||
return null;
|
||||
}
|
||||
|
||||
this.tables[table.name] = table;
|
||||
this.storageManager._log(`Table ${chalk.bold(table.name)} was ${chalk.bold('loaded')}.`, table);
|
||||
return table;
|
||||
|
||||
}
|
||||
|
||||
_error(err) {
|
||||
this.storageManager._error(err, this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = Provider;
|
5
src/structure/storage/interfaces/index.js
Normal file
5
src/structure/storage/interfaces/index.js
Normal file
@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
Provider: require('./Provider.js'),
|
||||
MongodbTable: require('./MongodbTable.js'),
|
||||
MariadbTable: require('./MariadbTable.js')
|
||||
};
|
76
src/structure/storage/providers/Mariadb.js
Normal file
76
src/structure/storage/providers/Mariadb.js
Normal file
@ -0,0 +1,76 @@
|
||||
const { Provider } = require('../interfaces/');
|
||||
const MySQL = require('mysql');
|
||||
|
||||
class MariaDBProvider extends Provider {
|
||||
|
||||
constructor(client, config) {
|
||||
|
||||
super(client, {
|
||||
name: 'mariadb',
|
||||
config
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
async initialize() {
|
||||
|
||||
try {
|
||||
|
||||
this.db = MySQL.createPool(this.config);
|
||||
|
||||
this.db.on('connection', async (connection) => {
|
||||
|
||||
// this.manager.logger.log('MariaDB connected.');
|
||||
|
||||
connection.on('error', (err) => {
|
||||
// this.manager.logger.error('MariaDB errored.', err);
|
||||
});
|
||||
|
||||
connection.on('close', (data) => {
|
||||
// this.manager.logger.log('MariaDB connection closed.', data);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
this.loaded = true;
|
||||
|
||||
} catch (err) {
|
||||
|
||||
// this.manager.logger.error('MariaDB connection failed.', err);
|
||||
|
||||
}
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
close() {
|
||||
this.db.end();
|
||||
}
|
||||
|
||||
/**
|
||||
* Query using SQL to MariaDB
|
||||
*
|
||||
* @param {string} query SQL query string.
|
||||
* @param {array<Object>} values Array of values to replace ? with in the query string
|
||||
* @returns {object} Returns an object containing the query result
|
||||
* @memberof MariaDBProvider
|
||||
*/
|
||||
query(query, values) {
|
||||
|
||||
if (!this.loaded) throw new Error('MariaDB not connected');
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
this.db.query(query, values, (err, result) => {
|
||||
if (err) reject(err);
|
||||
resolve(result);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = MariaDBProvider;
|
77
src/structure/storage/providers/Mongodb.js
Normal file
77
src/structure/storage/providers/Mongodb.js
Normal file
@ -0,0 +1,77 @@
|
||||
const { Provider } = require('../interfaces/');
|
||||
const { MongoClient } = require('mongodb');
|
||||
|
||||
class MongoDBProvider extends Provider {
|
||||
|
||||
constructor(client, config) {
|
||||
|
||||
super(client, {
|
||||
name: 'mongodb',
|
||||
config
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
async initialize() {
|
||||
|
||||
try {
|
||||
this.connection = await MongoClient.connect(process.env.MONGODB_HOST + this.config.database, { useUnifiedTopology: true });
|
||||
this.db = await this.connection.db(this.config.database);
|
||||
return this._initialized = true; //eslint-disable-line no-return-assign
|
||||
} catch (err) {
|
||||
this._error(err);
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
stats(options = {}) {
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
if (!this._initialized) return reject(new Error('MongoDB is not connected.'));
|
||||
this.db.stats(options, (error, result) => {
|
||||
if (error) reject(error);
|
||||
resolve(result);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
get guilds() {
|
||||
return this.tables.guilds;
|
||||
}
|
||||
|
||||
get permissions() {
|
||||
return this.tables.permissions;
|
||||
}
|
||||
|
||||
get infractions() {
|
||||
return this.tables.infractions;
|
||||
}
|
||||
|
||||
get messages() {
|
||||
return this.tables.messages;
|
||||
}
|
||||
|
||||
get attachments() {
|
||||
return this.tables.attachments;
|
||||
}
|
||||
|
||||
get users() {
|
||||
return this.tables.users;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line camelcase
|
||||
get role_cache() {
|
||||
return this.tables.role_cache;
|
||||
}
|
||||
|
||||
get webhooks() {
|
||||
return this.tables.webhooks;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = MongoDBProvider;
|
17
src/structure/storage/tables/mariadb/Activity.js
Normal file
17
src/structure/storage/tables/mariadb/Activity.js
Normal file
@ -0,0 +1,17 @@
|
||||
const { MariadbTable } = require('../../interfaces/');
|
||||
|
||||
class InfractionsTable extends MariadbTable {
|
||||
|
||||
constructor(client, provider) {
|
||||
|
||||
super(client, provider, {
|
||||
name: 'activity'
|
||||
});
|
||||
|
||||
this.provider = provider;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = InfractionsTable;
|
17
src/structure/storage/tables/mongodb/Infractions.js
Normal file
17
src/structure/storage/tables/mongodb/Infractions.js
Normal file
@ -0,0 +1,17 @@
|
||||
const { MongodbTable } = require('../../interfaces/');
|
||||
|
||||
class InfractionsTable extends MongodbTable {
|
||||
|
||||
constructor(client, provider) {
|
||||
|
||||
super(client, provider, {
|
||||
name: 'infractions'
|
||||
});
|
||||
|
||||
this.provider = provider;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = InfractionsTable;
|
Loading…
Reference in New Issue
Block a user