Skip to content
This repository has been archived by the owner on Jul 8, 2024. It is now read-only.

Add abstraction for SQL databases #26

Merged
merged 3 commits into from
Aug 3, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@
"discord.js": "^12.1.1",
"dotenv": "^8.1.0",
"eslint-plugin-jest": "^24.3.5",
"pg": "^7.12.1",
"pg": "^8.0.3",
"pg-mem": "^1.9.6",
"postgrator": "^4.0.1",
"sqlutils": "^1.2.1",
"typescript": "^4.1.5"
},
"devDependencies": {
Expand Down
37 changes: 13 additions & 24 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import Discord = require('discord.js');
import fs = require('fs');

import {prefix, ID, toID, pgPool} from './common';
import {prefix, ID, toID, database} from './common';
import {BaseCommand, BaseMonitor, DiscordChannel} from './command_base';
import {updateDatabase} from './database_version_control';
import * as child_process from 'child_process';
Expand Down Expand Up @@ -54,14 +54,12 @@ const userlist = new Set<string>();

export async function verifyData(data: Discord.Message | IDatabaseInsert) {
if (lockdown) return;
let worker = null;

// Server
if (data.guild && !servers.has(data.guild.id)) {
if (!worker) worker = await pgPool.connect();
const res = await worker.query('SELECT * FROM servers WHERE serverid = $1', [data.guild.id]);
if (!res.rows.length) {
await worker.query(
const res = await database.queryWithResults('SELECT * FROM servers WHERE serverid = $1', [data.guild.id]);
if (!res.length) {
await database.query(
'INSERT INTO servers (serverid, servername, logchannel, sticky) VALUES ($1, $2, $3, $4)',
[data.guild.id, data.guild.name, null, []]
);
Expand All @@ -72,10 +70,9 @@ export async function verifyData(data: Discord.Message | IDatabaseInsert) {
// Channel
if (data.guild && data.channel && ['text', 'news'].includes(data.channel.type) && !channels.has(data.channel.id)) {
const channel = (data.channel as Discord.TextChannel | Discord.NewsChannel);
if (!worker) worker = await pgPool.connect();
const res = await worker.query('SELECT * FROM channels WHERE channelid = $1', [channel.id]);
if (!res.rows.length) {
await worker.query(
const res = await database.queryWithResults('SELECT * FROM channels WHERE channelid = $1', [channel.id]);
if (!res.length) {
await database.query(
'INSERT INTO channels (channelid, channelname, serverid) VALUES ($1, $2, $3)',
[channel.id, channel.name, data.guild.id]
);
Expand All @@ -85,10 +82,9 @@ export async function verifyData(data: Discord.Message | IDatabaseInsert) {

// User
if (data.author && !users.has(data.author.id)) {
if (!worker) worker = await pgPool.connect();
const res = await worker.query('SELECT * FROM users WHERE userid = $1', [data.author.id]);
if (!res.rows.length) {
await worker.query(
const res = await database.queryWithResults('SELECT * FROM users WHERE userid = $1', [data.author.id]);
if (!res.length) {
await database.query(
'INSERT INTO users (userid, name, discriminator) VALUES ($1, $2, $3)',
[data.author.id, data.author.username, data.author.discriminator]
);
Expand All @@ -102,22 +98,19 @@ export async function verifyData(data: Discord.Message | IDatabaseInsert) {
await data.guild.members.fetch();
const userInServer = data.guild.members.cache.has(data.author.id);
if (userInServer) {
if (!worker) worker = await pgPool.connect();
const res = await worker.query(
const res = await database.queryWithResults(
'SELECT * FROM userlist WHERE serverid = $1 AND userid = $2',
[data.guild.id, data.author.id]
);
if (!res.rows.length) {
await worker.query(
if (!res.length) {
await database.query(
'INSERT INTO userlist (serverid, userid, boosting, sticky) VALUES ($1, $2, $3, $4)',
[data.guild.id, data.author.id, null, []]
);
}
userlist.add(data.guild.id + ',' + data.author.id);
}
}

if (worker) worker.release();
}

export const client = new Discord.Client();
Expand Down Expand Up @@ -199,8 +192,6 @@ client.on('message', (m) => void (async msg => {
} catch (e) {
await onError(e, 'A chat monitor crashed: ');
}
// Release any workers regardless of the result
monitor.releaseWorker(true);
}
return;
}
Expand All @@ -225,8 +216,6 @@ client.on('message', (m) => void (async msg => {
'\u274C - An error occured while trying to run your command. The error has been logged, and we will fix it soon.'
);
}
// Release any workers regardless of the result
cmd.releaseWorker(true);
})(m));

// Setup crash handlers
Expand Down
21 changes: 3 additions & 18 deletions src/command_base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
* execution in general.
*/
import Discord = require('discord.js');
import {prefix, toID, pgPool} from './common';
import {PoolClient} from 'pg';
import {prefix, toID, database} from './common';
import {client, verifyData} from './app';

export type DiscordChannel = Discord.TextChannel | Discord.NewsChannel;
Expand All @@ -32,7 +31,6 @@ export abstract class BaseCommand {
protected author: Discord.User;
protected channel: DiscordChannel;
protected guild: Discord.Guild | null;
protected worker: PoolClient | null;
protected isMonitor: boolean;

/**
Expand All @@ -46,7 +44,6 @@ export abstract class BaseCommand {
this.author = message.author;
this.channel = (message.channel as DiscordChannel);
this.guild = message.guild;
this.worker = null;
this.isMonitor = false;
}

Expand Down Expand Up @@ -293,22 +290,10 @@ export abstract class BaseCommand {
*/
protected async sendLog(msg: string | Discord.MessageEmbed): Promise<Discord.Message | void> {
if (!toID(msg) || !this.guild) return;
const res = await pgPool.query('SELECT logchannel FROM servers WHERE serverid = $1', [this.guild.id]);
const channel = this.getChannel(res.rows[0].logchannel, false, false);
const res = await database.queryWithResults('SELECT logchannel FROM servers WHERE serverid = $1', [this.guild.id]);
const channel = this.getChannel(res[0].logchannel, false, false);
if (channel) await channel.send(msg);
}

/**
* Used by app.ts to release a PoolClient in the event
* a command using one crashes
*/
releaseWorker(warn = false): void {
if (this.worker) {
if (warn) console.warn(`Releasing PG worker for ${this.isMonitor ? 'monitor' : 'command'}: ${this.cmd}`);
this.worker.release();
this.worker = null;
}
}
}

/**
Expand Down
18 changes: 5 additions & 13 deletions src/commands/activity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* channel activity monitors.
*/
import Discord = require('discord.js');
import {prefix, toID, pgPool} from '../common';
import {prefix, toID, database} from '../common';
import {BaseCommand, ReactionPageTurner, DiscordChannel, IAliasList} from '../command_base';

const ENGLISH_MONTH_NAMES = [
Expand Down Expand Up @@ -200,9 +200,7 @@ export class Leaderboard extends BaseCommand {
}

query += ' GROUP BY u.name, u.discriminator ORDER BY SUM(l.lines) desc;';
const res = await pgPool.query(query, args);

return res.rows;
return database.queryWithResults(query, args);
}

async execute() {
Expand Down Expand Up @@ -269,9 +267,7 @@ export class ChannelLeaderboard extends BaseCommand {
}

query += ' GROUP BY ch.channelname ORDER BY SUM(cl.lines) desc;';
const res = await pgPool.query(query, args);

return res.rows;
return database.queryWithResults(query, args);
}

async execute() {
Expand Down Expand Up @@ -333,9 +329,7 @@ export class Linecount extends BaseCommand {
let query = `SELECT ${key ? key + ' AS time, ' : ''}SUM(l.lines) FROM lines l WHERE l.serverid = $1 AND l.userid = $2`;
if (key) query += ` GROUP BY ${key} ORDER BY ${key} desc;`;
const args = [this.guild.id, id];
const res = await pgPool.query(query, args);

return res.rows;
return database.queryWithResults(query, args);
}

async execute() {
Expand Down Expand Up @@ -401,9 +395,7 @@ export class ChannelLinecount extends BaseCommand {
query += ' WHERE ch.serverid = $1 AND ch.channelid = $2';
if (key) query += ` GROUP BY ${key} ORDER BY ${key} desc;`;
const args = [this.guild.id, id];
const res = await pgPool.query(query, args);

return res.rows;
return database.queryWithResults(query, args);
}

async execute() {
Expand Down
45 changes: 22 additions & 23 deletions src/commands/boosts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,15 @@
*/
import Discord = require('discord.js');
import {client, verifyData} from '../app';
import {prefix, pgPool} from '../common';
import {prefix, database} from '../common';
import {BaseCommand, ReactionPageTurner, DiscordChannel} from '../command_base';

async function updateBoosters() {
const worker = await pgPool.connect();

for (const [guildId, guild] of client.guilds.cache) {
const res = await worker.query('SELECT userid FROM userlist WHERE serverid = $1 AND boosting IS NOT NULL', [guildId]);
const boosting = res.rows.map(r => r.userid);
const logchannelResult = await pgPool.query('SELECT logchannel FROM servers WHERE serverid = $1', [guildId]);
const logChannel = client.channels.cache.get(logchannelResult.rows[0].logchannel) as DiscordChannel;
const res = await database.queryWithResults('SELECT userid FROM userlist WHERE serverid = $1 AND boosting IS NOT NULL', [guildId]);
const boosting = res.map(r => r.userid);
const logchannelResult = await database.queryWithResults('SELECT logchannel FROM servers WHERE serverid = $1', [guildId]);
const logChannel = client.channels.cache.get(logchannelResult[0].logchannel) as DiscordChannel;
await guild.members.fetch();

for (const [id, gm] of guild.members.cache) {
Expand All @@ -30,45 +28,43 @@ async function updateBoosters() {
});

// Check if booster is in users table/userlist
if (!(await worker.query('SELECT userid FROM users WHERE userid = $1', [id])).rows.length) {
await worker.query(
if (!(await database.queryWithResults('SELECT userid FROM users WHERE userid = $1', [id])).length) {
await database.query(
'INSERT INTO users (userid, name, discriminator) VALUES ($1, $2, $3)',
[gm.user.id, gm.user.username, gm.user.discriminator]
);
}

const users = await worker.query('SELECT userid FROM userlist WHERE userid = $1 AND serverid = $2', [id, guildId]);
if (!users.rows.length) {
const users = await database.queryWithResults('SELECT userid FROM userlist WHERE userid = $1 AND serverid = $2', [id, guildId]);
if (!users.length) {
// Insert with update
await worker.query(
await database.query(
'INSERT INTO userlist (serverid, userid, boosting) VALUES ($1, $2, $3)',
[guildId, id, gm.premiumSince]
);
} else {
// Just update
await worker.query(
await database.query(
'UPDATE userlist SET boosting = $1 WHERE serverid = $2 AND userid = $3',
[gm.premiumSince, guildId, id]
);
}
await logChannel?.send(`<@${id}> has started boosting!`);
} else {
if (!boosting.includes(id)) continue; // Was not bosting before
await worker.query('UPDATE userlist SET boosting = NULL WHERE serverid = $1 AND userid = $2', [guildId, id]);
await database.query('UPDATE userlist SET boosting = NULL WHERE serverid = $1 AND userid = $2', [guildId, id]);
await logChannel?.send(`<@${id}> is no longer boosting.`);
boosting.splice(boosting.indexOf(id), 1);
}
}

// Anyone left in boosting left the server and is no longer boosting
for (const desterter of boosting) {
await worker.query('UPDATE userlist SET boosting = NULL WHERE serverid = $1 AND userid = $2', [guildId, desterter]);
await logChannel?.send(`<@${desterter}> is no longer boosting because they left the server.`);
for (const deserter of boosting) {
await database.query('UPDATE userlist SET boosting = NULL WHERE serverid = $1 AND userid = $2', [guildId, deserter]);
await logChannel?.send(`<@${deserter}> is no longer boosting because they left the server.`);
}
}

worker.release();

// Schedule next boost check
const nextCheck = new Date();
nextCheck.setDate(nextCheck.getDate() + 1);
Expand Down Expand Up @@ -133,17 +129,20 @@ export class Boosters extends BaseCommand {
}

async execute() {
if (!this.guild) return this.errorReply('This command is not mean\'t to be used in PMs.');
if (!this.guild) return this.errorReply('This command is not meant to be used in PMs.');
if (!(await this.can('MANAGE_ROLES'))) return this.errorReply('Access Denied.');

const res = await pgPool.query('SELECT u.name, u.discriminator, ul.boosting ' +
const res = await database.queryWithResults(
'SELECT u.name, u.discriminator, ul.boosting ' +
'FROM users u ' +
'INNER JOIN userlist ul ON u.userid = ul.userid ' +
'INNER JOIN servers s ON s.serverid = ul.serverid ' +
'WHERE s.serverid = $1 AND ul.boosting IS NOT NULL ' +
'ORDER BY ul.boosting', [this.guild.id]);
'ORDER BY ul.boosting',
[this.guild.id]
);

const page = new BoostPage(this.channel, this.author, this.guild, res.rows);
const page = new BoostPage(this.channel, this.author, this.guild, res);
await page.initialize(this.channel);
}

Expand Down
18 changes: 9 additions & 9 deletions src/commands/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/
import Discord = require('discord.js');
import {shutdown} from '../app';
import {prefix, pgPool} from '../common';
import {prefix, database} from '../common';
import {BaseCommand, IAliasList} from '../command_base';
import * as child_process from 'child_process';
let updateLock = false;
Expand Down Expand Up @@ -55,13 +55,13 @@ export class Query extends BaseCommand {

async execute() {
if (!(await this.can('EVAL'))) return this.errorReply('You do not have permission to do that.');
pgPool.query(this.target, (err, res) => {
if (err) {
void this.sendCode(`An error occured: ${err.toString()}`);
} else {
void this.sendCode(this.formatResponse(res.rows));
}
});

try {
const res = await database.queryWithResults(this.target, undefined);
await this.sendCode(this.formatResponse(res));
} catch (err) {
await this.sendCode(`An error occured: ${err.toString()}`);
}
}

private formatResponse(rows: any[]): string {
Expand Down Expand Up @@ -127,7 +127,7 @@ export class Shutdown extends BaseCommand {
}, 10000);

// empty the pool of database workers
await pgPool.end();
await database.destroy();

// exit
process.exit();
Expand Down
Loading