Switch to diesel with postgres for the database

Signed-off-by: trivernis <trivernis@protonmail.com>
pull/2/head
trivernis 3 years ago
parent 62d9664b35
commit 6d75b840b4
Signed by: Trivernis
GPG Key ID: DFFFCC2C7A02DB45

3
.gitignore vendored

@ -2,4 +2,5 @@
bot.db
.env
*.env
logs
logs
.idea

8
.idea/.gitignore vendored

@ -1,8 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
# Editor-based HTTP Client requests
/httpRequests/

@ -1,79 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CsvFileAttributes">
<option name="attributeMap">
<map>
<entry key="/src/commands/minecraft/mod.rs">
<value>
<Attribute>
<option name="separator" value=":" />
</Attribute>
</value>
</entry>
<entry key="/src/commands/music/current.rs">
<value>
<Attribute>
<option name="separator" value=":" />
</Attribute>
</value>
</entry>
<entry key="/src/commands/music/mod.rs">
<value>
<Attribute>
<option name="separator" value=":" />
</Attribute>
</value>
</entry>
<entry key="/src/commands/music/play.rs">
<value>
<Attribute>
<option name="separator" value=":" />
</Attribute>
</value>
</entry>
<entry key="/src/commands/music/play_next.rs">
<value>
<Attribute>
<option name="separator" value=":" />
</Attribute>
</value>
</entry>
<entry key="/src/commands/music/utils.rs">
<value>
<Attribute>
<option name="separator" value=":" />
</Attribute>
</value>
</entry>
<entry key="/src/database/guild.rs">
<value>
<Attribute>
<option name="separator" value="," />
</Attribute>
</value>
</entry>
<entry key="/src/database/scripts/update_tables.sql">
<value>
<Attribute>
<option name="separator" value="," />
</Attribute>
</value>
</entry>
<entry key="/src/main.rs">
<value>
<Attribute>
<option name="separator" value=":" />
</Attribute>
</value>
</entry>
<entry key="/src/providers/ytdl/mod.rs">
<value>
<Attribute>
<option name="separator" value=":" />
</Attribute>
</value>
</entry>
</map>
</option>
</component>
</project>

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="bot.db" uuid="4d1d7a8d-098d-4916-b51c-e7bde2613eda">
<driver-ref>sqlite.xerial</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
<jdbc-url>jdbc:sqlite:bot.db</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
</component>
</project>

@ -1,10 +0,0 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="DuplicatedCode" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<Languages>
<language minSize="60" name="Rust" />
</Languages>
</inspection_tool>
</profile>
</component>

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/tobi-rs.iml" filepath="$PROJECT_DIR$/.idea/tobi-rs.iml" />
</modules>
</component>
</project>

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="SqlDialectMappings">
<file url="file://$PROJECT_DIR$/src/database/scripts/create_tables.sql" dialect="GenericSQL" />
<file url="file://$PROJECT_DIR$/src/database/scripts/update_tables.sql" dialect="SQLite" />
<file url="PROJECT" dialect="SQLite" />
</component>
</project>

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="CPP_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

99
Cargo.lock generated

@ -358,6 +358,54 @@ dependencies = [
"num_cpus",
]
[[package]]
name = "database"
version = "0.1.0"
dependencies = [
"chrono",
"diesel",
"diesel_migrations",
"dotenv",
"log 0.4.14",
"r2d2",
"thiserror",
]
[[package]]
name = "diesel"
version = "1.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "047bfc4d5c3bd2ef6ca6f981941046113524b9a9f9a7cbdfdd7ff40f58e6f542"
dependencies = [
"bitflags 1.2.1",
"byteorder",
"chrono",
"diesel_derives",
"pq-sys",
"r2d2",
]
[[package]]
name = "diesel_derives"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45f5098f628d02a7a0f68ddba586fb61e80edec3bdc1be3b921f4ceec60858d3"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "diesel_migrations"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf3cde8413353dc7f5d72fa8ce0b99a560a359d2c5ef1e5817ca731cd9008f4c"
dependencies = [
"migrations_internals",
"migrations_macros",
]
[[package]]
name = "digest"
version = "0.9.0"
@ -983,6 +1031,27 @@ dependencies = [
"autocfg",
]
[[package]]
name = "migrations_internals"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b4fc84e4af020b837029e017966f86a1c2d5e83e64b589963d5047525995860"
dependencies = [
"diesel",
]
[[package]]
name = "migrations_macros"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9753f12909fd8d923f75ae5c3258cae1ed3c8ec052e1b38c93c21a6d157f789c"
dependencies = [
"migrations_internals",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "mime"
version = "0.3.16"
@ -1276,6 +1345,15 @@ version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
[[package]]
name = "pq-sys"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ac25eee5a0582f45a67e837e350d784e7003bd29a5f460796772061ca49ffda"
dependencies = [
"vcpkg",
]
[[package]]
name = "proc-macro-hack"
version = "0.5.19"
@ -1306,6 +1384,17 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "r2d2"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "545c5bc2b880973c9c10e4067418407a0ccaa3091781d1671d46eb35107cb26f"
dependencies = [
"log 0.4.14",
"parking_lot",
"scheduled-thread-pool",
]
[[package]]
name = "rand"
version = "0.7.3"
@ -1568,6 +1657,15 @@ dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "scheduled-thread-pool"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc6f74fd1204073fa02d5d5d68bec8021be4c38690b61264b2fdb48083d0e7d7"
dependencies = [
"parking_lot",
]
[[package]]
name = "scoped-tls"
version = "1.0.0"
@ -2002,6 +2100,7 @@ dependencies = [
"aspotify",
"chrono",
"colored",
"database",
"dotenv",
"fern",
"futures",

@ -27,4 +27,5 @@ log = "0.4.14"
fern = "0.6.0"
chrono = "0.4.19"
colored = "2.0.0"
sysinfo = "0.16.5"
sysinfo = "0.16.5"
database = {path="./database"}

@ -0,0 +1,2 @@
target
.env

340
database/Cargo.lock generated

@ -0,0 +1,340 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "autocfg"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "bitflags"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
[[package]]
name = "byteorder"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
dependencies = [
"libc",
"num-integer",
"num-traits",
"time",
"winapi",
]
[[package]]
name = "database"
version = "0.1.0"
dependencies = [
"chrono",
"diesel",
"diesel_migrations",
"dotenv",
"log",
"r2d2",
"thiserror",
]
[[package]]
name = "diesel"
version = "1.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "047bfc4d5c3bd2ef6ca6f981941046113524b9a9f9a7cbdfdd7ff40f58e6f542"
dependencies = [
"bitflags",
"byteorder",
"chrono",
"diesel_derives",
"pq-sys",
"r2d2",
]
[[package]]
name = "diesel_derives"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45f5098f628d02a7a0f68ddba586fb61e80edec3bdc1be3b921f4ceec60858d3"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "diesel_migrations"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf3cde8413353dc7f5d72fa8ce0b99a560a359d2c5ef1e5817ca731cd9008f4c"
dependencies = [
"migrations_internals",
"migrations_macros",
]
[[package]]
name = "dotenv"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f"
[[package]]
name = "instant"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec"
dependencies = [
"cfg-if",
]
[[package]]
name = "libc"
version = "0.2.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9385f66bf6105b241aa65a61cb923ef20efc665cb9f9bb50ac2f0c4b7f378d41"
[[package]]
name = "lock_api"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a3c91c24eae6777794bb1997ad98bbb87daf92890acab859f7eaa4320333176"
dependencies = [
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
dependencies = [
"cfg-if",
]
[[package]]
name = "migrations_internals"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b4fc84e4af020b837029e017966f86a1c2d5e83e64b589963d5047525995860"
dependencies = [
"diesel",
]
[[package]]
name = "migrations_macros"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9753f12909fd8d923f75ae5c3258cae1ed3c8ec052e1b38c93c21a6d157f789c"
dependencies = [
"migrations_internals",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "num-integer"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
dependencies = [
"autocfg",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
dependencies = [
"autocfg",
]
[[package]]
name = "parking_lot"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb"
dependencies = [
"instant",
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018"
dependencies = [
"cfg-if",
"instant",
"libc",
"redox_syscall",
"smallvec",
"winapi",
]
[[package]]
name = "pq-sys"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ac25eee5a0582f45a67e837e350d784e7003bd29a5f460796772061ca49ffda"
dependencies = [
"vcpkg",
]
[[package]]
name = "proc-macro2"
version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a152013215dca273577e18d2bf00fa862b89b24169fb78c4c95aeb07992c9cec"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
dependencies = [
"proc-macro2",
]
[[package]]
name = "r2d2"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "545c5bc2b880973c9c10e4067418407a0ccaa3091781d1671d46eb35107cb26f"
dependencies = [
"log",
"parking_lot",
"scheduled-thread-pool",
]
[[package]]
name = "redox_syscall"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94341e4e44e24f6b591b59e47a8a027df12e008d73fd5672dbea9cc22f4507d9"
dependencies = [
"bitflags",
]
[[package]]
name = "scheduled-thread-pool"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc6f74fd1204073fa02d5d5d68bec8021be4c38690b61264b2fdb48083d0e7d7"
dependencies = [
"parking_lot",
]
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "smallvec"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e"
[[package]]
name = "syn"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48fe99c6bd8b1cc636890bcc071842de909d902c81ac7dab53ba33c421ab8ffb"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "thiserror"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0f4a65597094d4483ddaed134f409b2cb7c1beccf25201a9f73c719254fa98e"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7765189610d8241a44529806d6fd1f2e0a08734313a35d5b3a556f92b381f3c0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "time"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
dependencies = [
"libc",
"wasi",
"winapi",
]
[[package]]
name = "unicode-xid"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
[[package]]
name = "vcpkg"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b00bca6106a5e23f3eee943593759b7fcddb00554332e856d990c893966879fb"
[[package]]
name = "wasi"
version = "0.10.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

@ -0,0 +1,16 @@
[package]
name = "database"
version = "0.1.0"
authors = ["trivernis <trivernis@protonmail.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
dotenv = "0.15.0"
chrono = "0.4.19"
thiserror = "1.0.24"
diesel = {version="1.4.6", features=["postgres", "r2d2", "chrono"]}
log = "0.4.14"
diesel_migrations = "1.4.0"
r2d2 = "0.8.9"

@ -0,0 +1,5 @@
# For documentation on how to configure this file,
# see diesel.rs/guides/configuring-diesel-cli
[print_schema]
file = "src/schema.rs"

@ -0,0 +1,6 @@
-- This file was automatically created by Diesel to setup helper functions
-- and other internal bookkeeping. This file is safe to edit, any future
-- changes will be added to existing projects as new migrations.
DROP FUNCTION IF EXISTS diesel_manage_updated_at(_tbl regclass);
DROP FUNCTION IF EXISTS diesel_set_updated_at();

@ -0,0 +1,36 @@
-- This file was automatically created by Diesel to setup helper functions
-- and other internal bookkeeping. This file is safe to edit, any future
-- changes will be added to existing projects as new migrations.
-- Sets up a trigger for the given table to automatically set a column called
-- `updated_at` whenever the row is modified (unless `updated_at` was included
-- in the modified columns)
--
-- # Example
--
-- ```sql
-- CREATE TABLE users (id SERIAL PRIMARY KEY, updated_at TIMESTAMP NOT NULL DEFAULT NOW());
--
-- SELECT diesel_manage_updated_at('users');
-- ```
CREATE OR REPLACE FUNCTION diesel_manage_updated_at(_tbl regclass) RETURNS VOID AS $$
BEGIN
EXECUTE format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s
FOR EACH ROW EXECUTE PROCEDURE diesel_set_updated_at()', _tbl);
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION diesel_set_updated_at() RETURNS trigger AS $$
BEGIN
IF (
NEW IS DISTINCT FROM OLD AND
NEW.updated_at IS NOT DISTINCT FROM OLD.updated_at
) THEN
NEW.updated_at := current_timestamp;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;

@ -0,0 +1,2 @@
-- This file should undo anything in `up.sql`
DROP TABLE guild_settings;

@ -0,0 +1,6 @@
CREATE TABLE guild_settings (
guild_id BIGINT NOT NULL,
key VARCHAR(255) NOT NULL,
value VARCHAR(1024),
PRIMARY KEY (guild_id, key)
);

@ -0,0 +1,81 @@
use crate::error::DatabaseResult;
use crate::models::*;
use crate::schema::*;
use crate::PoolConnection;
use diesel::insert_into;
use diesel::prelude::*;
use std::any;
use std::fmt::Debug;
use std::str::FromStr;
#[derive(Clone)]
pub struct Database {
pool: PoolConnection,
}
unsafe impl Send for Database {}
unsafe impl Sync for Database {}
impl Database {
pub fn new(pool: PoolConnection) -> Self {
Self { pool }
}
/// Returns a guild setting from the database
pub fn get_guild_setting<T: 'static>(
&self,
guild_id: u64,
key: &str,
) -> DatabaseResult<Option<T>>
where
T: FromStr,
{
use guild_settings::dsl;
log::debug!("Retrieving setting '{}' for guild {}", key, guild_id);
let connection = self.pool.get()?;
let entries: Vec<GuildSetting> = dsl::guild_settings
.filter(dsl::guild_id.eq(guild_id as i64))
.filter(dsl::key.eq(key))
.load::<GuildSetting>(&connection)?;
log::trace!("Result is {:?}", entries);
if let Some(first) = entries.first() {
if any::TypeId::of::<T>() == any::TypeId::of::<bool>() {
Ok(first
.value
.clone()
.unwrap_or("false".to_string())
.parse::<T>()
.ok())
} else {
Ok(first.value.clone().and_then(|v| v.parse::<T>().ok()))
}
} else {
return Ok(None);
}
}
/// Upserting a guild setting
pub fn set_guild_setting<T>(&self, guild_id: u64, key: &str, value: T) -> DatabaseResult<()>
where
T: ToString + Debug,
{
use guild_settings::dsl;
log::debug!("Setting '{}' to '{:?}' for guild {}", key, value, guild_id);
let connection = self.pool.get()?;
insert_into(dsl::guild_settings)
.values(&GuildSettingInsert {
guild_id: guild_id as i64,
key: key.to_string(),
value: value.to_string(),
})
.on_conflict((dsl::guild_id, dsl::key))
.do_update()
.set(dsl::value.eq(value.to_string()))
.execute(&connection)?;
Ok(())
}
}

@ -0,0 +1,21 @@
use thiserror::Error;
pub type DatabaseResult<T> = Result<T, DatabaseError>;
#[derive(Error, Debug)]
pub enum DatabaseError {
#[error("DotEnv Error: {0}")]
DotEnv(#[from] dotenv::Error),
#[error("Connection Error: {0}")]
ConnectionError(#[from] diesel::prelude::ConnectionError),
#[error("Pool Connection Error: {0}")]
PoolConnectionError(#[from] r2d2::Error),
#[error("Migration Error: {0}")]
MigrationError(#[from] diesel_migrations::RunMigrationsError),
#[error("Result Error: {0}")]
ResultError(#[from] diesel::result::Error),
}

@ -0,0 +1,41 @@
#[macro_use]
extern crate diesel;
#[macro_use]
extern crate diesel_migrations;
use crate::error::DatabaseResult;
use diesel::prelude::*;
use diesel::r2d2::{ConnectionManager, Pool};
use std::env;
pub mod database;
pub mod error;
pub mod models;
pub mod schema;
pub use database::Database;
type PoolConnection = Pool<ConnectionManager<PgConnection>>;
embed_migrations!("../database/migrations");
fn get_connection() -> DatabaseResult<PoolConnection> {
dotenv::dotenv()?;
let database_url = env::var("DATABASE_URL").expect("No DATABASE_URL in path");
log::debug!("Establishing database connection...");
let manager = ConnectionManager::<PgConnection>::new(database_url);
let pool = Pool::builder().max_size(16).build(manager)?;
let connection = pool.get()?;
log::debug!("Running migrations...");
embedded_migrations::run(&connection)?;
log::debug!("Migrations finished");
log::info!("Database connection initialized");
Ok(pool)
}
pub fn get_database() -> DatabaseResult<Database> {
let conn = get_connection()?;
Ok(Database::new(conn))
}

@ -0,0 +1,16 @@
use crate::schema::*;
#[derive(Queryable, Debug)]
pub struct GuildSetting {
pub guild_id: i64,
pub key: String,
pub value: Option<String>,
}
#[derive(Insertable, Debug)]
#[table_name = "guild_settings"]
pub struct GuildSettingInsert {
pub guild_id: i64,
pub key: String,
pub value: String,
}

@ -0,0 +1,7 @@
table! {
guild_settings (guild_id, key) {
guild_id -> Int8,
key -> Varchar,
value -> Nullable<Varchar>,
}
}

@ -7,10 +7,10 @@ use serenity::Client;
use songbird::SerenityInit;
use crate::commands::*;
use crate::database::get_database;
use crate::handler::Handler;
use crate::utils::context_data::{DatabaseContainer, Store, StoreData};
use crate::utils::error::{BotError, BotResult};
use crate::utils::store::{Store, StoreData};
use database::get_database;
use serenity::model::id::UserId;
use std::collections::HashSet;
@ -25,7 +25,8 @@ pub async fn get_client() -> BotResult<Client> {
.await?;
{
let mut data = client.data.write().await;
data.insert::<Store>(StoreData::new(database))
data.insert::<Store>(StoreData::new());
data.insert::<DatabaseContainer>(database);
}
Ok(client)

@ -2,7 +2,7 @@ use serenity::client::Context;
use serenity::framework::standard::{macros::command, Args, CommandError, CommandResult};
use serenity::model::channel::Message;
use crate::utils::store::Store;
use crate::utils::context_data::Store;
#[command]
#[description("Provides information for a single enchantment")]

@ -2,7 +2,7 @@ use serenity::client::Context;
use serenity::framework::standard::{macros::command, Args, CommandError, CommandResult};
use serenity::model::channel::Message;
use crate::utils::store::Store;
use crate::utils::context_data::Store;
#[command]
#[description("Provides information for a single minecraft item")]

@ -27,8 +27,8 @@ use crate::providers::music::queue::{MusicQueue, Song};
use crate::providers::music::{
get_video_information, get_videos_for_playlist, search_video_information,
};
use crate::utils::context_data::Store;
use crate::utils::error::{BotError, BotResult};
use crate::utils::store::Store;
use regex::Regex;
use std::sync::atomic::{AtomicIsize, AtomicUsize, Ordering};
use std::time::Duration;

@ -7,8 +7,9 @@ use crate::commands::music::{
get_channel_for_author, get_queue_for_guild, get_songs_for_query, get_voice_manager,
join_channel, play_next_in_queue,
};
use crate::database::get_database_from_context;
use crate::database::guild::SETTING_AUTOSHUFFLE;
use crate::providers::constants::SETTING_AUTOSHUFFLE;
use crate::utils::context_data::get_database_from_context;
#[command]
#[only_in(guilds)]
@ -45,9 +46,8 @@ async fn play(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
queue_lock.add(song);
}
let database = get_database_from_context(ctx).await;
let database_lock = database.lock().await;
let autoshuffle = database_lock
.get_guild_setting(&guild.id, SETTING_AUTOSHUFFLE)
let autoshuffle = database
.get_guild_setting(guild.id.0, SETTING_AUTOSHUFFLE)?
.unwrap_or(false);
if autoshuffle {
log::debug!("Autoshuffeling");

@ -3,8 +3,8 @@ use serenity::framework::standard::macros::command;
use serenity::framework::standard::{Args, CommandResult};
use serenity::model::channel::Message;
use crate::database::get_database_from_context;
use crate::database::guild::GUILD_SETTINGS;
use crate::providers::constants::GUILD_SETTINGS;
use crate::utils::context_data::get_database_from_context;
#[command]
#[only_in(guilds)]
@ -21,17 +21,15 @@ async fn get(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
if let Some(key) = args.single::<String>().ok() {
log::debug!("Displaying guild setting of '{}'", key);
let database_lock = database.lock().await;
let setting = database_lock.get_guild_setting::<String>(&guild.id, &key);
let setting = database.get_guild_setting::<String>(guild.id.0, &key)?;
match setting {
Ok(value) => {
Some(value) => {
msg.channel_id
.say(ctx, format!("`{}` is set to to `{}`", key, value))
.await?;
}
Err(e) => {
eprintln!("Failed to get setting: {:?}", e);
None => {
msg.channel_id
.say(ctx, format!("`{}` is not set", key))
.await?;
@ -42,13 +40,9 @@ async fn get(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
for key in GUILD_SETTINGS {
let mut kv_pairs = Vec::new();
{
let database_lock = database.lock().await;
match database_lock.get_guild_setting::<String>(&guild.id, &key) {
Ok(value) => kv_pairs.push(format!("`{}` = `{}`", key, value)),
Err(e) => {
eprintln!("Failed to get setting: {:?}", e);
kv_pairs.push(format!("`{}` not set", key))
}
match database.get_guild_setting::<String>(guild.id.0, &key)? {
Some(value) => kv_pairs.push(format!("`{}` = `{}`", key, value)),
None => kv_pairs.push(format!("`{}` not set", key)),
}
}
msg.channel_id

@ -3,7 +3,8 @@ use serenity::framework::standard::macros::command;
use serenity::framework::standard::{Args, CommandResult};
use serenity::model::channel::Message;
use crate::database::get_database_from_context;
use crate::providers::constants::GUILD_SETTINGS;
use crate::utils::context_data::get_database_from_context;
#[command]
#[only_in(guilds)]
@ -14,12 +15,18 @@ use crate::database::get_database_from_context;
#[max_args(2)]
#[required_permissions("MANAGE_GUILD")]
async fn set(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
let key = args.single::<String>().unwrap();
let key = args.single::<String>().unwrap().to_lowercase();
if !GUILD_SETTINGS.contains(&&*key) {
msg.channel_id
.say(ctx, format!("Invalid setting `{}`", key))
.await?;
return Ok(());
}
let value = args.single::<String>().unwrap();
let database = get_database_from_context(ctx).await;
let database_lock = database.lock().await;
let guild = msg.guild(&ctx.cache).await.unwrap();
database_lock.set_guild_setting(&guild.id, &key, value.clone())?;
database.set_guild_setting(guild.id.0, &key, value.clone())?;
msg.channel_id
.say(ctx, format!("Set `{}` to `{}`", key, value))
.await?;

@ -1,16 +0,0 @@
use serde_derive::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, PartialEq)]
pub struct Guild {
guild_id: i32,
}
#[derive(Serialize, Deserialize, Debug, PartialEq)]
pub struct GuildSettings {
pub guild_id: String,
pub setting_key: String,
pub setting_value: String,
}
pub static SETTING_AUTOSHUFFLE: &str = "music.autoshuffle";
pub static GUILD_SETTINGS: &[&str] = &[SETTING_AUTOSHUFFLE];

@ -1,103 +0,0 @@
use std::str::FromStr;
use std::sync::Arc;
use rusqlite::{params, Connection, NO_PARAMS};
use serenity::client::Context;
use serenity::model::id::GuildId;
use tokio::sync::Mutex;
use crate::database::guild::GuildSettings;
use crate::database::scripts::{CREATE_SCRIPT, UPDATE_SCRIPT};
use crate::utils::error::{BotError, BotResult};
use crate::utils::store::Store;
use std::fmt::Debug;
pub mod guild;
pub mod scripts;
#[derive(Debug)]
pub struct Database {
connection: Connection,
}
impl Database {
pub fn new(connection: Connection) -> Self {
Self { connection }
}
/// Initializes the database
pub fn init(&self) -> BotResult<()> {
self.connection.execute(CREATE_SCRIPT, NO_PARAMS)?;
self.connection.execute(UPDATE_SCRIPT, NO_PARAMS)?;
log::info!("Database initialized");
Ok(())
}
/// Returns a guild setting
pub fn get_guild_setting<T>(&self, guild_id: &GuildId, key: &str) -> BotResult<T>
where
T: Clone + FromStr + Debug,
{
log::trace!(
"Fetching value of guild setting '{}' for guild {}",
key,
guild_id
);
self.connection
.query_row(
"SELECT guild_id, setting_key, setting_value FROM guild_settings WHERE guild_id = ?1 AND setting_key = ?2",
params![guild_id.to_string(), key],
|r| Ok(serde_rusqlite::from_row::<GuildSettings>(r).unwrap()),
)
.map_err(BotError::from)
.and_then(|s| {
s.setting_value
.parse::<T>()
.map_err(|_| BotError::from("Failed to parse Setting"))
})
}
/// Sets a guild setting and overrides it if it already exists
pub fn set_guild_setting<T>(&self, guild_id: &GuildId, key: &str, value: T) -> BotResult<()>
where
T: ToString + FromStr + Clone + Debug,
{
if self.get_guild_setting::<T>(guild_id, key).is_ok() {
log::trace!("Clearing previous guild setting");
self.connection.execute(
"DELETE FROM guild_settings WHERE guild_id = ?1 AND setting_key = ?2",
params![guild_id.to_string(), key],
)?;
}
self.connection.execute(
"INSERT INTO guild_settings (guild_id, setting_key, setting_value) VALUES (?1, ?2, ?3)",
params![guild_id.to_string(), key, value.to_string()],
)?;
log::debug!(
"Setting '{}' set to '{:?}' for guild {}",
key,
value,
guild_id
);
Ok(())
}
}
pub fn get_database() -> BotResult<Database> {
let filename = dotenv::var("DB_NAME").unwrap_or("bot.db".to_string());
let connection = rusqlite::Connection::open(filename)?;
let database = Database::new(connection);
database.init()?;
Ok(database)
}
/// Returns a reference to a guilds music queue
pub(crate) async fn get_database_from_context(ctx: &Context) -> Arc<Mutex<Database>> {
let data = ctx.data.read().await;
let store = data.get::<Store>().unwrap();
Arc::clone(&store.database)
}

@ -1,6 +0,0 @@
CREATE TABLE IF NOT EXISTS guild_settings
(
guild_id TEXT NOT NULL,
setting_key TEXT NOT NULL,
setting_value TEXT NOT NULL
);

@ -1,2 +0,0 @@
pub static CREATE_SCRIPT: &str = include_str!("create_tables.sql");
pub static UPDATE_SCRIPT: &str = include_str!("update_tables.sql");

@ -1,2 +0,0 @@
PRAGMA foreign_keys = false;
DROP TABLE IF EXISTS guilds;

@ -3,7 +3,6 @@ use crate::utils::logging::init_logger;
pub mod client;
mod commands;
pub mod database;
pub mod handler;
mod providers;
pub mod utils;

@ -0,0 +1,2 @@
pub const SETTING_AUTOSHUFFLE: &str = "music.autoshuffle";
pub const GUILD_SETTINGS: &[&str] = &[SETTING_AUTOSHUFFLE];

@ -1 +1,2 @@
pub(crate) mod constants;
pub(crate) mod music;

@ -5,23 +5,22 @@ use serenity::model::id::GuildId;
use serenity::prelude::TypeMapKey;
use tokio::sync::Mutex;
use crate::database::Database;
use crate::providers::music::queue::MusicQueue;
use crate::providers::music::spotify::SpotifyApi;
use database::Database;
use serenity::client::Context;
pub struct Store;
pub struct StoreData {
pub database: Arc<Mutex<Database>>,
pub minecraft_data_api: minecraft_data_rs::api::Api,
pub music_queues: HashMap<GuildId, Arc<Mutex<MusicQueue>>>,
pub spotify_api: SpotifyApi,
}
impl StoreData {
pub fn new(database: Database) -> StoreData {
pub fn new() -> StoreData {
Self {
database: Arc::new(Mutex::new(database)),
minecraft_data_api: minecraft_data_rs::api::Api::new(
minecraft_data_rs::api::versions::latest_stable().unwrap(),
),
@ -34,3 +33,19 @@ impl StoreData {
impl TypeMapKey for Store {
type Value = StoreData;
}
pub struct DatabaseContainer;
impl TypeMapKey for DatabaseContainer {
type Value = Database;
}
/// Returns a copy of the database
pub async fn get_database_from_context(ctx: &Context) -> Database {
let data = ctx.data.read().await;
let database = data
.get::<DatabaseContainer>()
.expect("Invalid Context setup: Missing database");
database.clone()
}

@ -7,8 +7,8 @@ pub enum BotError {
#[error("Serenity Error: {0}")]
SerenityError(#[from] serenity::Error),
#[error("Sqlite Error: {0}")]
Sqlite(#[from] rusqlite::Error),
#[error("Database Error: {0}")]
Database(#[from] database::error::DatabaseError),
#[error("Missing Bot Token")]
MissingToken,

@ -2,9 +2,9 @@ use std::collections::VecDeque;
use rand::Rng;
pub(crate) mod context_data;
pub(crate) mod error;
pub(crate) mod logging;
pub(crate) mod store;
/// Fisher-Yates shuffle for VecDeque
pub fn shuffle_vec_deque<T>(deque: &mut VecDeque<T>) {

Loading…
Cancel
Save