diff --git a/breadbot_test.db b/breadbot_test.db index f15a818..c7e3fc3 100644 Binary files a/breadbot_test.db and b/breadbot_test.db differ diff --git a/src/breadbot.ts b/src/breadbot.ts index 27e884c..2d7ab8f 100644 --- a/src/breadbot.ts +++ b/src/breadbot.ts @@ -1,9 +1,10 @@ -import { Client, Events, GatewayIntentBits, Guild, Interaction } from "discord.js" +import { Client, Events, GatewayIntentBits, Guild, GuildBasedChannel, Interaction } from "discord.js" import { utilities } from "./utilties" import { commands } from "./commands" import { config } from "./config" +import { SQLCommon } from "./utilties/storage/interfaces" -const client : Client = new Client({ +export const client : Client = new Client({ intents: [ GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages, @@ -11,12 +12,15 @@ const client : Client = new Client({ ] }) +let db: SQLCommon + if (config.DB_MODE == "sqlite") { - const db = new utilities.sqlite.SqliteDB("breadbot_test.db") + db = new utilities.sqlite.SqliteDB("breadbot_test.db") db.run("PRAGMA foreign_keys = ON") utilities.tables.makeTables(db) + utilities.tables.makeConstraints(db) } client.once(Events.ClientReady, () => { @@ -24,13 +28,24 @@ client.once(Events.ClientReady, () => { console.log("Breadbot is ready") client.guilds.cache.forEach(async (guild: Guild) => { - console.log(`ID: ${guild.id}, Name: ${guild.name}, Desc: ${guild.description}`) - await utilities.command.deployCommands(guild.id) + await utilities.commands.deployCommands(guild.id) + + // TODO handle failures? + await utilities.guilds.insertGuild(db, guild) + + guild.channels.cache.forEach(async (channel: GuildBasedChannel) => { + await utilities.channels.insertChannel(db, channel) + }) }) }) client.on(Events.GuildCreate, async (guild : Guild) => { - await utilities.command.deployCommands(guild.id) + await utilities.commands.deployCommands(guild.id) + await utilities.guilds.insertGuild(db, guild) + + guild.channels.cache.forEach(async (channel: GuildBasedChannel) => { + await utilities.channels.insertChannel(db, channel) + }) }) client.on(Events.InteractionCreate, async (interaction: Interaction) => { diff --git a/src/utilties/discord/channels.ts b/src/utilties/discord/channels.ts new file mode 100644 index 0000000..ebc340f --- /dev/null +++ b/src/utilties/discord/channels.ts @@ -0,0 +1,33 @@ +import { SQLCommon } from "../storage/interfaces"; +import { GuildBasedChannel } from "discord.js"; +import { SQLResult } from "../storage/enumerations"; + +export async function doesChannelExist(db: SQLCommon, channel : GuildBasedChannel) : Promise { + const queryResult : Object[] = await db.getAllParameterized( + "SELECT * FROM channels WHERE server_snowflake = ? AND channel_snowflake = ?", + [channel.guild.id, channel.id] + ) + + return queryResult.length != 0 +} + +export async function insertChannel(db: SQLCommon, channel: GuildBasedChannel) : Promise { + const alreadyExists: boolean = await doesChannelExist(db, channel) + + if(alreadyExists) { + return SQLResult.ALREADYEXISTS + } + + try { + await db.runParameterized( + "INSERT INTO channels VALUES (?, ?, ?, ?)", + [channel.id, channel.guild.id, channel.name, channel.isThread()] + ) + + return SQLResult.CREATED + } catch (err) { + //TODO Winston should handle this + console.log(err) + return SQLResult.FAILED + } +} \ No newline at end of file diff --git a/src/utilties/discord/command_utils.ts b/src/utilties/discord/commands.ts similarity index 100% rename from src/utilties/discord/command_utils.ts rename to src/utilties/discord/commands.ts diff --git a/src/utilties/discord/guilds.ts b/src/utilties/discord/guilds.ts new file mode 100644 index 0000000..4433cbf --- /dev/null +++ b/src/utilties/discord/guilds.ts @@ -0,0 +1,33 @@ +import { Guild } from "discord.js"; +import { SQLCommon } from "../storage/interfaces"; +import { SQLResult } from "../storage/enumerations"; + +export async function doesGuildExist(db: SQLCommon, guild : Guild) : Promise { + const queryResult : Object[] = await db.getAllParameterized( + "SELECT * FROM servers WHERE server_snowflake = ?", + [guild.id] + ) + + return queryResult.length != 0 +} + +export async function insertGuild(db: SQLCommon, guild: Guild) : Promise { + const alreadyExists: boolean = await doesGuildExist(db, guild) + + if (alreadyExists) { + return SQLResult.ALREADYEXISTS + } + try { + await db.runParameterized( + "INSERT INTO servers VALUES (?, ?, ?)", + [guild.id, guild.name, guild.description] + ) + + return SQLResult.CREATED + } catch (err) { + console.log("Insert Failed") + //TODO Winston should handle this + console.log(err) + return SQLResult.FAILED + } +} \ No newline at end of file diff --git a/src/utilties/index.ts b/src/utilties/index.ts index cce9d11..6f9e2ca 100644 --- a/src/utilties/index.ts +++ b/src/utilties/index.ts @@ -1,9 +1,13 @@ -import * as command from "./discord/command_utils" +import * as commands from "./discord/commands" import * as sqlite from "./storage/sqlite" import * as tables from "./storage/tables" +import * as guilds from "./discord/guilds" +import * as channels from "./discord/channels" export const utilities = { - command, + commands, sqlite, - tables + tables, + guilds, + channels } \ No newline at end of file diff --git a/src/utilties/storage/enumerations.ts b/src/utilties/storage/enumerations.ts new file mode 100644 index 0000000..ef7583c --- /dev/null +++ b/src/utilties/storage/enumerations.ts @@ -0,0 +1,7 @@ +export enum SQLResult { + CREATED, + UPDATED, + DELETED, + ALREADYEXISTS, + FAILED +} \ No newline at end of file diff --git a/src/utilties/storage/interfaces.ts b/src/utilties/storage/interfaces.ts index 5c82513..e5ee6ba 100644 --- a/src/utilties/storage/interfaces.ts +++ b/src/utilties/storage/interfaces.ts @@ -1,4 +1,4 @@ -export interface sql_common { +export interface SQLCommon { run(query: string) : Promise runParameterized(query: string, parameters: any[]): Promise getAll(query: string) : Promise diff --git a/src/utilties/storage/sqlite.ts b/src/utilties/storage/sqlite.ts index 2102604..33cc857 100644 --- a/src/utilties/storage/sqlite.ts +++ b/src/utilties/storage/sqlite.ts @@ -1,8 +1,8 @@ import * as sqlite3 from 'sqlite3' -import { sql_common } from "./interfaces" +import { SQLCommon } from "./interfaces" -export class SqliteDB implements sql_common { +export class SqliteDB implements SQLCommon { private db : sqlite3.Database; public constructor(private readonly dbName: string) { @@ -10,7 +10,7 @@ export class SqliteDB implements sql_common { } async run(query: string): Promise { - return new Promise(() => { + return new Promise((resolve, reject) => { this.db.run(query, (result : sqlite3.RunResult, err: Error) => { if (err) { // TODO Winston should handle this @@ -18,9 +18,9 @@ export class SqliteDB implements sql_common { throw err } else { if (result != null) { - return result.changes + resolve(result.changes) } else { - return 0 + resolve(0) } } }) @@ -28,42 +28,47 @@ export class SqliteDB implements sql_common { } async runParameterized(query: string, parameters: any[]): Promise { - return new Promise(() => { + return new Promise((resolve, reject) => { this.db.run(query, parameters, (result : sqlite3.RunResult, err: Error) => { if (err) { // TODO Winston should handle this console.log(err) throw err } else { - return result.changes + if (result != null) { + resolve(result.changes) + } else { + resolve(0) + } } }) }) } async getAll(query: string): Promise { - return new Promise(() => { + return new Promise((resolve, reject) => { this.db.all(query, (err: Error, rows: Object[]) => { if (err) { // TODO Winston should handle this console.log(err) throw err } else { - return rows + console.log("Got rows") + resolve(rows) } }) }) } getAllParameterized(query: string, parameters: any[]): Promise { - return new Promise(() => { + return new Promise((resolve, reject) => { this.db.all(query, parameters, (err: Error, rows: Object[]) => { if (err) { // TODO Winston should handle this console.log(err) throw err } else { - return rows + resolve(rows) } }) }) diff --git a/src/utilties/storage/tables.ts b/src/utilties/storage/tables.ts index 4304f56..ddb07fb 100644 --- a/src/utilties/storage/tables.ts +++ b/src/utilties/storage/tables.ts @@ -1,11 +1,22 @@ -import { sql_common } from "./interfaces"; +import { SQLCommon } from "./interfaces"; const tables: string[] = [ "CREATE TABLE IF NOT EXISTS servers (server_snowflake bigint NOT NULL PRIMARY KEY,server_name text NOT NULL,server_description mediumtext);", - "CREATE TABLE IF NOT EXISTS channels (channel_snowflake bigint NOT NULL PRIMARY KEY,server_snowflake bigint NOT NULL,channel_name text NOT NULL);" + "CREATE TABLE IF NOT EXISTS channels (channel_snowflake bigint NOT NULL PRIMARY KEY,server_snowflake bigint NOT NULL,channel_name text NOT NULL,is_thread bit NOT NULL);", + "CREATE TABLE IF NOT EXISTS messages (message_snowflake bigint NOT NULL PRIMARY KEY,channel_snowflake bigint NOT NULL,user_snowflake bigint NOT NULL,message_content longtext NOT NULL,message_timestamp datetime NOT NULL,message_deleted bit NOT NULL);", + "CREATE TABLE IF NOT EXISTS message_content_changes (message_change_id bigint NOT NULL PRIMARY KEY,message_snowflake bigint NOT NULL,message_change_old_timestamp datetime NOT NULL,message_change_old_content longtext NOT NULL);", + "CREATE TABLE IF NOT EXISTS message_attachments (attachment_snowflake bigint NOT NULL PRIMARY KEY,message_snowflake bigint NOT NULL,attachment_name text NOT NULL,attachment_description text,attachment_timestamp datetime NOT NULL,attachment_mime_type text,attachment_url text NOT NULL,attachment_downloaded bit NOT NULL);" ] -export async function makeTables(db: sql_common): Promise { +const constraints: string[] = [ + "ALTER TABLE channels ADD CONSTRAINT channels_server_snowflake_fk FOREIGN KEY (server_snowflake) REFERENCES servers (server_snowflake);" +] + +export async function makeTables(db: SQLCommon): Promise { return Promise.all(tables.map((statement) => db.run(statement))) } +export async function makeConstraints(db: SQLCommon): Promise { + return Promise.all(constraints.map((statement) => db.run(statement))) +} +