feat(bots/discord): add a better way to manage databases

This commit is contained in:
PalmDevs
2024-03-29 17:17:14 +07:00
parent f9d50a0a6b
commit a68d726875

View File

@@ -1,12 +1,75 @@
import { Database, type SQLQueryBindings, type Statement } from 'bun:sqlite' import { Database } from 'bun:sqlite'
type BasicSQLBindings = string | number | null
export class BasicDatabase<T extends Record<string, BasicSQLBindings>> {
#db: Database
#table: string
constructor(file: string, struct: string, tableName = 'data') {
const db = new Database(file, {
create: true,
readwrite: true,
})
this.#db = db
this.#table = tableName
db.run(`CREATE TABLE IF NOT EXISTS ${tableName} (${struct});`)
}
run(statement: string) {
this.#db.run(statement)
}
prepare(statement: string) {
return this.#db.prepare<T, BasicSQLBindings[]>(statement)
}
query(statement: string) {
return this.#db.query<T, BasicSQLBindings[]>(statement)
}
insert(...values: BasicSQLBindings[]) {
this.run(`INSERT INTO ${this.#table} VALUES (${values.map(this.#encodeValue).join(', ')});`)
}
update(what: Partial<T>, where: string) {
const set = Object.entries(what)
.map(([key, value]) => `${key} = ${this.#encodeValue(value)}`)
.join(', ')
this.run(`UPDATE ${this.#table} SET ${set} WHERE ${where};`)
}
delete(where: string) {
this.run(`DELETE FROM ${this.#table} WHERE ${where};`)
}
select(columns: string[] | string, where: string) {
const realColumns = Array.isArray(columns) ? columns.join(', ') : columns
return this.query(`SELECT ${realColumns} FROM ${this.#table} WHERE ${where};`).get()
}
#encodeValue(value: unknown) {
if (typeof value === 'string') return `'${value}'`
if (typeof value === 'number') return value
if (typeof value === 'boolean') return value ? 1 : 0
if (value === null) return 'NULL'
return null
}
}
export class LabeledResponseDatabase { export class LabeledResponseDatabase {
readonly tableName = 'labeledResponses' #db: BasicDatabase<LabeledResponse>
readonly tableStruct = `
reply TEXT PRIMARY KEY NOT NULL, constructor() {
this.#db = new BasicDatabase<LabeledResponse>(
'responses.db',
`reply TEXT PRIMARY KEY NOT NULL,
channel TEXT NOT NULL, channel TEXT NOT NULL,
guild TEXT NOT NULL, guild TEXT NOT NULL,
referenceMessage TEXT NOT NULL, referenceMessage TEXT KEY NOT NULL,
label TEXT NOT NULL, label TEXT NOT NULL,
text TEXT NOT NULL, text TEXT NOT NULL,
correctedBy TEXT, correctedBy TEXT,
@@ -14,59 +77,21 @@ export class LabeledResponseDatabase {
typeof("text") = 'text' AND typeof("text") = 'text' AND
length("text") > 0 AND length("text") > 0 AND
length("text") <= 280 length("text") <= 280
)`,
) )
`
#statements: {
save: Statement
edit: Statement
get: Statement<LabeledResponse, SQLQueryBindings[]>
}
constructor() {
// TODO: put in config
const db = new Database('responses.db', {
create: true,
readwrite: true,
})
db.run(`CREATE TABLE IF NOT EXISTS ${this.tableName} (
${this.tableStruct}
);`)
this.#statements = {
save: db.prepare(
`INSERT INTO ${this.tableName} VALUES ($reply, $channel, $guild, $reference, $label, $text, NULL);`,
),
edit: db.prepare(
`UPDATE ${this.tableName} SET label = $label, correctedBy = $correctedBy WHERE reply = $reply`,
),
get: db.prepare(`SELECT * FROM ${this.tableName} WHERE reply = $reply`),
} as const
} }
save({ reply, channel, guild, referenceMessage, label, text }: Omit<LabeledResponse, 'correctedBy'>) { save({ reply, channel, guild, referenceMessage, label, text }: Omit<LabeledResponse, 'correctedBy'>) {
const actualText = text.slice(0, 280) const actualText = text.slice(0, 280)
this.#statements.save.run({ this.#db.insert(reply, channel, guild, referenceMessage, label, actualText, null)
$reply: reply,
$channel: channel,
$guild: guild,
$reference: referenceMessage,
$label: label,
$text: actualText,
})
} }
get(reply: string) { get(reply: string) {
return this.#statements.get.get({ $reply: reply }) return this.#db.select('*', `reply = ${reply}`)
} }
edit(reply: string, { label, correctedBy }: Pick<LabeledResponse, 'label' | 'correctedBy'>) { edit(reply: string, { label, correctedBy }: Pick<LabeledResponse, 'label' | 'correctedBy'>) {
this.#statements.edit.run({ this.#db.update({ label, correctedBy }, `reply = ${reply}`)
$reply: reply,
$label: label,
$correctedBy: correctedBy,
})
} }
} }