A ton of fixes and it's now possible to track servers and channels in the DB

This commit is contained in:
Bradley Bickford 2025-06-26 21:01:11 -04:00
parent d9bcdb766c
commit a247bfb35e
10 changed files with 132 additions and 24 deletions

Binary file not shown.

View File

@ -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 { utilities } from "./utilties"
import { commands } from "./commands" import { commands } from "./commands"
import { config } from "./config" import { config } from "./config"
import { SQLCommon } from "./utilties/storage/interfaces"
const client : Client = new Client({ export const client : Client = new Client({
intents: [ intents: [
GatewayIntentBits.Guilds, GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMessages, GatewayIntentBits.GuildMessages,
@ -11,12 +12,15 @@ const client : Client = new Client({
] ]
}) })
let db: SQLCommon
if (config.DB_MODE == "sqlite") { 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") db.run("PRAGMA foreign_keys = ON")
utilities.tables.makeTables(db) utilities.tables.makeTables(db)
utilities.tables.makeConstraints(db)
} }
client.once(Events.ClientReady, () => { client.once(Events.ClientReady, () => {
@ -24,13 +28,24 @@ client.once(Events.ClientReady, () => {
console.log("Breadbot is ready") console.log("Breadbot is ready")
client.guilds.cache.forEach(async (guild: Guild) => { client.guilds.cache.forEach(async (guild: Guild) => {
console.log(`ID: ${guild.id}, Name: ${guild.name}, Desc: ${guild.description}`) await utilities.commands.deployCommands(guild.id)
await utilities.command.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) => { 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) => { client.on(Events.InteractionCreate, async (interaction: Interaction) => {

View File

@ -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<boolean> {
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<SQLResult> {
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
}
}

View File

@ -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<boolean> {
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<SQLResult> {
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
}
}

View File

@ -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 sqlite from "./storage/sqlite"
import * as tables from "./storage/tables" import * as tables from "./storage/tables"
import * as guilds from "./discord/guilds"
import * as channels from "./discord/channels"
export const utilities = { export const utilities = {
command, commands,
sqlite, sqlite,
tables tables,
guilds,
channels
} }

View File

@ -0,0 +1,7 @@
export enum SQLResult {
CREATED,
UPDATED,
DELETED,
ALREADYEXISTS,
FAILED
}

View File

@ -1,4 +1,4 @@
export interface sql_common { export interface SQLCommon {
run(query: string) : Promise<number> run(query: string) : Promise<number>
runParameterized(query: string, parameters: any[]): Promise<number> runParameterized(query: string, parameters: any[]): Promise<number>
getAll(query: string) : Promise<Object[]> getAll(query: string) : Promise<Object[]>

View File

@ -1,8 +1,8 @@
import * as sqlite3 from 'sqlite3' 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; private db : sqlite3.Database;
public constructor(private readonly dbName: string) { public constructor(private readonly dbName: string) {
@ -10,7 +10,7 @@ export class SqliteDB implements sql_common {
} }
async run(query: string): Promise<number> { async run(query: string): Promise<number> {
return new Promise(() => { return new Promise((resolve, reject) => {
this.db.run(query, (result : sqlite3.RunResult, err: Error) => { this.db.run(query, (result : sqlite3.RunResult, err: Error) => {
if (err) { if (err) {
// TODO Winston should handle this // TODO Winston should handle this
@ -18,9 +18,9 @@ export class SqliteDB implements sql_common {
throw err throw err
} else { } else {
if (result != null) { if (result != null) {
return result.changes resolve(result.changes)
} else { } else {
return 0 resolve(0)
} }
} }
}) })
@ -28,42 +28,47 @@ export class SqliteDB implements sql_common {
} }
async runParameterized(query: string, parameters: any[]): Promise<number> { async runParameterized(query: string, parameters: any[]): Promise<number> {
return new Promise(() => { return new Promise((resolve, reject) => {
this.db.run(query, parameters, (result : sqlite3.RunResult, err: Error) => { this.db.run(query, parameters, (result : sqlite3.RunResult, err: Error) => {
if (err) { if (err) {
// TODO Winston should handle this // TODO Winston should handle this
console.log(err) console.log(err)
throw err throw err
} else { } else {
return result.changes if (result != null) {
resolve(result.changes)
} else {
resolve(0)
}
} }
}) })
}) })
} }
async getAll(query: string): Promise<Object[]> { async getAll(query: string): Promise<Object[]> {
return new Promise(() => { return new Promise((resolve, reject) => {
this.db.all(query, (err: Error, rows: Object[]) => { this.db.all(query, (err: Error, rows: Object[]) => {
if (err) { if (err) {
// TODO Winston should handle this // TODO Winston should handle this
console.log(err) console.log(err)
throw err throw err
} else { } else {
return rows console.log("Got rows")
resolve(rows)
} }
}) })
}) })
} }
getAllParameterized(query: string, parameters: any[]): Promise<Object[]> { getAllParameterized(query: string, parameters: any[]): Promise<Object[]> {
return new Promise(() => { return new Promise((resolve, reject) => {
this.db.all(query, parameters, (err: Error, rows: Object[]) => { this.db.all(query, parameters, (err: Error, rows: Object[]) => {
if (err) { if (err) {
// TODO Winston should handle this // TODO Winston should handle this
console.log(err) console.log(err)
throw err throw err
} else { } else {
return rows resolve(rows)
} }
}) })
}) })

View File

@ -1,11 +1,22 @@
import { sql_common } from "./interfaces"; import { SQLCommon } from "./interfaces";
const tables: string[] = [ 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 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<number[]> { 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<number[]> {
return Promise.all(tables.map((statement) => db.run(statement))) return Promise.all(tables.map((statement) => db.run(statement)))
} }
export async function makeConstraints(db: SQLCommon): Promise<number[]> {
return Promise.all(constraints.map((statement) => db.run(statement)))
}