add support for MySQL and SQLite3 with sqlz (Sequelize)

Signed-off-by: Rajdeep Malakar <rajdeepm.dev@gmail.com>
This commit is contained in:
Rajdeep Malakar 2024-03-08 14:33:46 +05:30
parent 7a88d053e1
commit ff93a9d7ee
No known key found for this signature in database
GPG Key ID: BB9202F5967BAC9C
16 changed files with 1637 additions and 150 deletions

5
.gitignore vendored
View File

@ -128,3 +128,8 @@ dist
.yarn/build-state.yml .yarn/build-state.yml
.yarn/install-state.gz .yarn/install-state.gz
.pnp.* .pnp.*
# Cx-specific
package-lock.json
db.sql
db.sql-journal

View File

@ -0,0 +1,59 @@
{
"xan.guilds": {
"uuid": "text",
"serverId": "text",
"channelId": "text",
"networks": "text",
"client": "text"
},
"xan.messages": {
"uuid": "text",
"authorName": "text",
"authorId": "text",
"authorIcon": "text",
"serverIcon": "text",
"channelId": "text",
"message": "text"
},
"xan.userBans": {
"uuid": "text",
"userId": "text",
"mod": "text",
"reason": "text"
},
"xan.messageDelivery": {
"uuid": "text",
"channelId": "text",
"messageId": "text",
"client": "text",
"link": "text"
},
"xan.serverBans": {
"uuid": "text",
"serverId": "text",
"mod": "text",
"reason": "text"
},
"xan.userPrefs": {
"uuid": "text",
"userId": "text",
"prefs": "text"
},
"xan.serverPrefs": {
"uuid": "text",
"serverId": "text",
"prefs": "text"
},
"xan.networks": {
"uuid": "text",
"networkId": "text",
"address": "text"
}
}

View File

@ -5,5 +5,6 @@
"xan.messages", "xan.messages",
"xan.messageDelivery", "xan.messageDelivery",
"xan.networks", "xan.networks",
"xan.server_prefs" "xan.serverPrefs",
"xan.userPrefs"
] ]

View File

@ -10,22 +10,22 @@ module.exports.initMongoDBInstance = async (client,config,callback) => {
var call_data = { var call_data = {
createStore: async (name, callback) => { createStore: async (name, callback) => {
db.createCollection(name).then(() => { db.createCollection(name).then(async () => {
callback(true); await callback(true);
}).catch(() => { }).catch(() => {
callback(false); await callback(false);
}) })
}, },
list: async (table , data, callback) => { list: async (table, data, callback) => {
var collection = db.collection(table); var collection = db.collection(table);
var found_data = await collection.find(data).toArray(); var found_data = await collection.find(data).toArray();
callback(found_data); await callback(found_data);
}, },
insert: async (table, data,callback) => { insert: async (table, data, callback) => {
var uuid = require('node:crypto').randomUUID(); var uuid = require('node:crypto').randomUUID();
var collection = db.collection(table); var collection = db.collection(table);
@ -35,26 +35,26 @@ module.exports.initMongoDBInstance = async (client,config,callback) => {
...data ...data
}) })
callback(uuid) await callback(uuid)
}, },
delete: async (table, uuid , callback) => { delete: async (table, uuid, callback) => {
var collection = db.collection(table); var collection = db.collection(table);
collection.deleteOne({ uuid: uuid }); collection.deleteOne({ uuid: uuid });
callback(true); await callback(true);
}, },
search: async (table, data , callback) => { search: async (table, data, callback) => {
var collection = db.collection(table); var collection = db.collection(table);
var found_data = await collection.findOne(data); var found_data = await collection.findOne(data);
callback(found_data); await callback(found_data);
}, },
edit: async (table, uuid, data , callback) => { edit: async (table, uuid, data, callback) => {
var collection = db.collection(table); var collection = db.collection(table);
collection.updateOne({ collection.updateOne({
@ -64,15 +64,15 @@ module.exports.initMongoDBInstance = async (client,config,callback) => {
uuid: uuid, uuid: uuid,
...data ...data
} }
}) });
callback(true); await callback(true);
}, },
build: async () => { build: async () => {
var tables = require("./collection_tables.json"); var tables = require("./collection_tables.json");
for(var table of tables){ for(var table of tables){
call_data.createStore(table , (state) => {}); call_data.createStore(table, (state) => {});
} }
} }
}; };

View File

@ -1,77 +0,0 @@
/**
*
* @param {import('mysql2').Connection} client
* @param {*} callback
*/
module.exports.initMYSQL2Connection = async (client,config,callback) => {
var call_data = {
createStore: async (name, callback) => {
client.query(
`CREATE TABLE ${name.replace(".","")} (id INT NOT NULL AUTO_INCREMENT , uuid VARCHAR(256) NOT NULL , data LONGTEXT NOT NULL, timestamp VARCHAR(16) NOT NULL , PRIMARY KEY (id)) ENGINE = ${config.database_engine};`,
async function(err,results,fields) {
callback(true);
}
)
},
list: async (table , callback) => {
var fetchData = [];
client.query(
`SELECT * FROM ${table.replace(".","")}`,
async function(err,results,fields) {
for(var element of results) {
var data = JSON.parse(element['data']);
await fetchData.push({
uuid: element['uuid'],
...data
});
await callback(fetchData);
}
}
)
},
search: async (table , data , callback) => {
var fetchData = [];
client.query(
`SELECT * FROM ${table.replace(".","")}`,
async function(err,results,fields) {
for(var element of results) {
var data = JSON.parse(element['data']);
if(data)
await fetchData.push({
uuid: element['uuid'],
...data
});
await callback(fetchData);
}
}
)
},
insert: async (table, data,callback) => {
var uuid = require('node:crypto').randomUUID();
client.query(
`INSERT INTO ${table.replace(".","")} (uuid, data, timestamp) VALUES
('${uuid}', '${JSON.stringify(data)}', '${Date.now()}')`,
async function(err,result,fields) {
callback(uuid)
}
)
},
build: async () => {
var tables = require("./collection_tables.json");
for(var table of tables){
call_data.createStore(table , (state) => {});
}
}
}
callback(call_data);
}

30
database/schemas.js Normal file
View File

@ -0,0 +1,30 @@
var schema = require("./collection_schemas.json");
const { DataTypes } = require("sequelize");
let sql = fix_schema_sql();
let mongod = {};
function sql_schema(type) {
switch (type) {
case "text":
return DataTypes.TEXT;
default:
return DataTypes.TEXT;
}
}
function fix_schema_sql() {
let __schema = {};
for (let key of Object.keys(schema)) {
let obj = {};
for (let k of Object.keys(schema[key])) {
obj[k] = sql_schema(schema[key][k]);
}
__schema[key.replaceAll(".", "__")] = obj;
}
return __schema;
}
module.exports = {
sql, mongod
};

71
database/sqlz.js Normal file
View File

@ -0,0 +1,71 @@
const { Sequelize } = require("sequelize");
/**
*
* @param {Sequelize} client
* @param {*} callback
*/
module.exports.initSQLConnection = async (client, config, callback) => {
let tables = {};
var call_data = {
list: async (table, data, callback) => {
console.log(`Listing ${table}`);
let result = await tables[table].findAll({ where: data });
if (!result) {
await callback(result);
} else {
await callback(result.map(r => r.dataValues));
}
},
delete: async (table, uuid, callback) => {
await tables[table].destroy({ where: { uuid: uuid } });
await callback(true);
},
search: async (table, data, callback) => {
console.log(`Searching ${table}`);
var result = await tables[table].findOne({ where: data });
await callback(result);
},
insert: async (table, data, callback) => {
console.log(`Inserting to ${table}`);
var uuid = require('node:crypto').randomUUID();
let instance = tables[table].build({
uuid: uuid,
...data
});
await instance.save();
await callback(uuid);
},
edit: async (table, uuid, data, callback) => {
try {
await tables[table].update({
uuid: uuid,
...data
}, {
where: { uuid: uuid }
});
await callback(true);
} catch (e) {
await callback(false);
console.error(`Error:\n${e}`);
}
},
build: async () => {
var __tables = require("./collection_tables.json");
var __schema = require("./schemas.js");
var schema = __schema.sql;
for (var table of __tables) {
const model = client.define(table.replaceAll(".", "__"), schema[table.replaceAll(".", "__")], { freezeTableName: true });
tables[table] = model;
}
await client.sync();
}
}
callback(call_data);
}

1387
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -18,6 +18,8 @@
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
"mongodb": "^6.4.0", "mongodb": "^6.4.0",
"mysql2": "^3.9.2", "mysql2": "^3.9.2",
"nodemon": "^3.1.0" "nodemon": "^3.1.0",
"sequelize": "^6.37.1",
"sqlite3": "^5.1.7"
} }
} }

View File

@ -16,18 +16,19 @@ module.exports = {
async execute(interaction) { async execute(interaction) {
var channel = interaction.options.getChannel("channel"); var channel = interaction.options.getChannel("channel");
interaction.client.database.search("xan.guilds" , { interaction.client.database.search("xan.guilds", {
serverId : interaction.guild.id serverId: interaction.guild.id
} , async (data) => { }, async (data) => {
console.log(data);
if(data == null){ if(data == null){
interaction.client.database.insert("xan.guilds" , { interaction.client.database.insert("xan.guilds", {
serverId : interaction.guild.id, serverId: interaction.guild.id,
channelId : channel.id, channelId: channel.id,
client: "discord" client: "discord"
}, async (c) => { }, async (c) => {
await interaction.reply(`Saved Channel as #${channel.name} in Database as Discord Client`); await interaction.reply(`Saved Channel as #${channel.name} in Database as Discord Client`);
}) })
}else{ } else {
await interaction.reply(`Guild is already saved into Database`); await interaction.reply(`Guild is already saved into Database`);
} }
}); });

View File

@ -8,7 +8,7 @@ module.exports = {
discord_client_secret: process.env['discord_client_secret'] || "", discord_client_secret: process.env['discord_client_secret'] || "",
// Database Configurations // Database Configurations
// Possible Options ["mongod" , "mysqld"] // Possible Options ["mongod", "mysqld", "sqlite"]
database_adapter: "mongod", database_adapter: "mongod",
// If using MongoDB // If using MongoDB
database_uri: process.env['database_uri'] || "", database_uri: process.env['database_uri'] || "",
@ -17,5 +17,7 @@ module.exports = {
database_host: "localhost", database_host: "localhost",
database_user: "root", database_user: "root",
database_password: process.env['database_password'] || "", database_password: process.env['database_password'] || "",
database_engine: "InnoDB" database_engine: "InnoDB",
// If using SQLite3
database_file: process.env["DATABASE_FILE"] || ""
} }

View File

@ -4,7 +4,7 @@ const config = require("./config/prod");
const fs = require('node:fs'); const fs = require('node:fs');
const path = require('node:path'); const path = require('node:path');
const { MongoClient } = require("mongodb"); const { MongoClient } = require("mongodb");
const mysql = require('mysql2'); const { Sequelize } = require("sequelize");
const { Client, Events, GatewayIntentBits, Collection } = require('discord.js'); const { Client, Events, GatewayIntentBits, Collection } = require('discord.js');
const client = new Client({ const client = new Client({
@ -25,18 +25,30 @@ async function main() {
require("../database/mongod").initMongoDBInstance(mongo_client, config, (back) => initMessageManager(back)); require("../database/mongod").initMongoDBInstance(mongo_client, config, (back) => initMessageManager(back));
} else if (config.database_adapter == "mysqld") { } else if (config.database_adapter == "mysqld") {
const mysql_connection = mysql.createConnection({ const mysql_conn = new Sequelize(config.database_name, config.database_user, config.database_password, {
host: config.database_host, host: config.database_host,
user: config.database_user, dialect: "mysql"
password: config.database_password, })
database: config.database_name, try {
}); await mysql_conn.authenticate();
await require("../database/sqlz")
mysql_connection.connect(); .initSQLConnection(mysql_conn, config, async (back) => await initMessageManager(back));
mysql_connection.on('error', (error) => console.error); } catch (e) {
console.error(`Error:\n${e}`);
require("../database/mysqld").initMYSQL2Connection(mysql_connection, config, (back) => initMessageManager(back)); }
} } else if (config.database_adapter == "sqlite") {
const sql_conn = new Sequelize({
dialect: "sqlite",
storage: config.database_file
});
try {
await sql_conn.authenticate();
await require("../database/sqlz")
.initSQLConnection(sql_conn, config, async (back) => await initMessageManager(back));
} catch (e) {
console.error(`Error:\n${e}`);
}
}
} }
async function initMessageManager(database) { async function initMessageManager(database) {

View File

@ -10,7 +10,7 @@ class Message {
* @property {string|null} message * @property {string|null} message
*/ */
constructor(){ constructor() {
this.serverId = 1; this.serverId = 1;
this.serverName = "[No Server]"; this.serverName = "[No Server]";
this.serverIcon = null; this.serverIcon = null;
@ -24,7 +24,7 @@ class Message {
* Fill Product fields with new values * Fill Product fields with new values
* @param {MessageFields} newFields - Object containing new values for Category fields * @param {MessageFields} newFields - Object containing new values for Category fields
*/ */
fill(newFields) { fill (newFields) {
for (let field in newFields) { for (let field in newFields) {
if (this.hasOwnProperty(field) && newFields.hasOwnProperty(field)) { if (this.hasOwnProperty(field) && newFields.hasOwnProperty(field)) {
if (this[field] !== 'undefined') { if (this[field] !== 'undefined') {
@ -36,12 +36,12 @@ class Message {
return this.toArray(); return this.toArray();
} }
toArray(){ toArray() {
return { return {
serverId : this.serverId, serverId: this.serverId,
serverName : this.serverName, serverName: this.serverName,
serverIcon : this.serverIcon, serverIcon: this.serverIcon,
authorId : this.authorId, authorId: this.authorId,
authorIcon: this.authorIcon, authorIcon: this.authorIcon,
authorName: this.authorName, authorName: this.authorName,
message: this.message message: this.message

View File

@ -7,7 +7,7 @@ const { EmbedBuilder } = require("@discordjs/builders");
* @param {Message} message * @param {Message} message
* @param {Client} discord * @param {Client} discord
*/ */
const send = (message, database, channelId , uuid , discord) => { const send = (message, database, channelId, uuid, discord) => {
discord.channels.cache?.get(channelId).send({ discord.channels.cache?.get(channelId).send({
embeds: [ embeds: [
new EmbedBuilder() new EmbedBuilder()
@ -35,9 +35,9 @@ const send = (message, database, channelId , uuid , discord) => {
}).catch((error) => { }).catch((error) => {
console.log(`Unable to Send Message to Channel [${channelId}]`); console.log(`Unable to Send Message to Channel [${channelId}]`);
}).then((_message) => { }).then((_message) => {
database.insert("xan.messageDelivery" ,{ database.insert("xan.messageDelivery", {
channelId: _message.channelId, channelId: _message.channelId,
messageId : `${_message.id}`, messageId: `${_message.id}`,
client: "discord", client: "discord",
link: uuid link: uuid
}, (uuid) => {}); }, (uuid) => {});

View File

@ -13,11 +13,11 @@ const actionMessageManager = async (
) => { ) => {
var d_data = { var d_data = {
find_uuid: (messageId, authorId , cb) => { find_uuid: (messageId, authorId , cb) => {
database.search("xan.messageDelivery" , { messageId: messageId } , (dataUid) => { database.search("xan.messageDelivery", { messageId }, (dataUid) => {
database.search("xan.messages" , { uuid: dataUid.link } , (data) => { database.search("xan.messages", { uuid: dataUid.link }, (data) => {
if(data.authorId.toString() == authorId.toString()){ if (data.authorId.toString() == authorId.toString()) {
cb(dataUid.link); cb(dataUid.link);
}else{ } else {
cb(null) cb(null)
} }
}) })
@ -27,7 +27,7 @@ const actionMessageManager = async (
delete: (uuid) => { delete: (uuid) => {
database.list("xan.messageDelivery", { database.list("xan.messageDelivery", {
link: uuid link: uuid
} , (data) => { }, (data) => {
for(var message of data) { for(var message of data) {
discord.channels.fetch(message['channelId']).then((channel) => { discord.channels.fetch(message['channelId']).then((channel) => {
channel.messages.delete(message['messageId']).catch(e => { channel.messages.delete(message['messageId']).catch(e => {

View File

@ -10,20 +10,20 @@ const initMessageManager = async (
database, database,
discord discord
) => { ) => {
function postMessage(message, uuid){ function postMessage(message, uuid) {
database.list("xan.guilds" , {} , (list) => { database.list("xan.guilds", {}, (list) => {
for(var element of list){ for (var element of list) {
if(element.client == "discord") discordSend(message , database , element.channelId ,uuid , discord); if (element.client == "discord") discordSend(message, database, element.channelId, uuid, discord);
} }
}); });
} }
discord.on(Events.MessageCreate , (message) => { discord.on(Events.MessageCreate, (message) => {
if(message.author.bot) return; if(message.author.bot) return;
database.search("xan.guilds" , { database.search("xan.guilds", {
serverId : message.guild.id serverId : message.guild.id
} , async (data) => { }, async (data) => {
if(data !== null){ if(data !== null){
message.delete().catch((error) => {}); message.delete().catch((error) => {});
@ -37,7 +37,7 @@ const initMessageManager = async (
_message.serverIcon = message.guild.iconURL(); _message.serverIcon = message.guild.iconURL();
_message.message = message.content; _message.message = message.content;
database.insert("xan.messages" , _message.toArray() , (uuid) => { database.insert("xan.messages", _message.toArray(), (uuid) => {
postMessage(_message.toArray(), uuid); postMessage(_message.toArray(), uuid);
}); });
} }