From 6d75b840b4569ed416906647a0385ab9265241ae Mon Sep 17 00:00:00 2001 From: trivernis Date: Fri, 9 Apr 2021 11:39:15 +0200 Subject: [PATCH] Switch to diesel with postgres for the database Signed-off-by: trivernis --- .gitignore | 3 +- .idea/.gitignore | 8 - .idea/csv-plugin.xml | 79 ---- .idea/dataSources.xml | 12 - .idea/inspectionProfiles/Project_Default.xml | 10 - .idea/modules.xml | 8 - .idea/sqldialects.xml | 8 - .idea/tobi-rs.iml | 11 - .idea/vcs.xml | 6 - Cargo.lock | 99 +++++ Cargo.toml | 3 +- database/.gitignore | 2 + database/Cargo.lock | 340 ++++++++++++++++++ database/Cargo.toml | 16 + database/diesel.toml | 5 + database/migrations/.gitkeep | 0 .../down.sql | 6 + .../up.sql | 36 ++ .../2021-04-09-075309_create_tables/down.sql | 2 + .../2021-04-09-075309_create_tables/up.sql | 6 + database/src/database.rs | 81 +++++ database/src/error.rs | 21 ++ database/src/lib.rs | 41 +++ database/src/models.rs | 16 + database/src/schema.rs | 7 + src/client.rs | 7 +- src/commands/minecraft/enchantment.rs | 2 +- src/commands/minecraft/item.rs | 2 +- src/commands/music/mod.rs | 2 +- src/commands/music/play.rs | 10 +- src/commands/settings/get.rs | 22 +- src/commands/settings/set.rs | 15 +- src/database/guild.rs | 16 - src/database/mod.rs | 103 ------ src/database/scripts/create_tables.sql | 6 - src/database/scripts/mod.rs | 2 - src/database/scripts/update_tables.sql | 2 - src/main.rs | 1 - src/providers/constants.rs | 2 + src/providers/mod.rs | 1 + src/utils/{store.rs => context_data.rs} | 23 +- src/utils/error.rs | 4 +- src/utils/mod.rs | 2 +- 43 files changed, 738 insertions(+), 310 deletions(-) delete mode 100644 .idea/.gitignore delete mode 100644 .idea/csv-plugin.xml delete mode 100644 .idea/dataSources.xml delete mode 100644 .idea/inspectionProfiles/Project_Default.xml delete mode 100644 .idea/modules.xml delete mode 100644 .idea/sqldialects.xml delete mode 100644 .idea/tobi-rs.iml delete mode 100644 .idea/vcs.xml create mode 100644 database/.gitignore create mode 100644 database/Cargo.lock create mode 100644 database/Cargo.toml create mode 100644 database/diesel.toml create mode 100644 database/migrations/.gitkeep create mode 100644 database/migrations/00000000000000_diesel_initial_setup/down.sql create mode 100644 database/migrations/00000000000000_diesel_initial_setup/up.sql create mode 100644 database/migrations/2021-04-09-075309_create_tables/down.sql create mode 100644 database/migrations/2021-04-09-075309_create_tables/up.sql create mode 100644 database/src/database.rs create mode 100644 database/src/error.rs create mode 100644 database/src/lib.rs create mode 100644 database/src/models.rs create mode 100644 database/src/schema.rs delete mode 100644 src/database/guild.rs delete mode 100644 src/database/mod.rs delete mode 100644 src/database/scripts/create_tables.sql delete mode 100644 src/database/scripts/mod.rs delete mode 100644 src/database/scripts/update_tables.sql create mode 100644 src/providers/constants.rs rename src/utils/{store.rs => context_data.rs} (62%) diff --git a/.gitignore b/.gitignore index 12508d9..1fb366e 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ bot.db .env *.env -logs \ No newline at end of file +logs +.idea \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 73f69e0..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -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/ diff --git a/.idea/csv-plugin.xml b/.idea/csv-plugin.xml deleted file mode 100644 index 623c7ae..0000000 --- a/.idea/csv-plugin.xml +++ /dev/null @@ -1,79 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml deleted file mode 100644 index c2235b4..0000000 --- a/.idea/dataSources.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - sqlite.xerial - true - org.sqlite.JDBC - jdbc:sqlite:bot.db - $ProjectFileDir$ - - - \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml deleted file mode 100644 index a3f5ff6..0000000 --- a/.idea/inspectionProfiles/Project_Default.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index bbf9b9e..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/sqldialects.xml b/.idea/sqldialects.xml deleted file mode 100644 index 0d136c5..0000000 --- a/.idea/sqldialects.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/tobi-rs.iml b/.idea/tobi-rs.iml deleted file mode 100644 index c254557..0000000 --- a/.idea/tobi-rs.iml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 94a25f7..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 871d90c..a1659cf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/Cargo.toml b/Cargo.toml index 863f32f..bd68c2d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,4 +27,5 @@ log = "0.4.14" fern = "0.6.0" chrono = "0.4.19" colored = "2.0.0" -sysinfo = "0.16.5" \ No newline at end of file +sysinfo = "0.16.5" +database = {path="./database"} \ No newline at end of file diff --git a/database/.gitignore b/database/.gitignore new file mode 100644 index 0000000..796603f --- /dev/null +++ b/database/.gitignore @@ -0,0 +1,2 @@ +target +.env \ No newline at end of file diff --git a/database/Cargo.lock b/database/Cargo.lock new file mode 100644 index 0000000..9e4766e --- /dev/null +++ b/database/Cargo.lock @@ -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" diff --git a/database/Cargo.toml b/database/Cargo.toml new file mode 100644 index 0000000..6f34ac2 --- /dev/null +++ b/database/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "database" +version = "0.1.0" +authors = ["trivernis "] +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" \ No newline at end of file diff --git a/database/diesel.toml b/database/diesel.toml new file mode 100644 index 0000000..92267c8 --- /dev/null +++ b/database/diesel.toml @@ -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" diff --git a/database/migrations/.gitkeep b/database/migrations/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/database/migrations/00000000000000_diesel_initial_setup/down.sql b/database/migrations/00000000000000_diesel_initial_setup/down.sql new file mode 100644 index 0000000..a9f5260 --- /dev/null +++ b/database/migrations/00000000000000_diesel_initial_setup/down.sql @@ -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(); diff --git a/database/migrations/00000000000000_diesel_initial_setup/up.sql b/database/migrations/00000000000000_diesel_initial_setup/up.sql new file mode 100644 index 0000000..d68895b --- /dev/null +++ b/database/migrations/00000000000000_diesel_initial_setup/up.sql @@ -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; diff --git a/database/migrations/2021-04-09-075309_create_tables/down.sql b/database/migrations/2021-04-09-075309_create_tables/down.sql new file mode 100644 index 0000000..c54e2c1 --- /dev/null +++ b/database/migrations/2021-04-09-075309_create_tables/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +DROP TABLE guild_settings; \ No newline at end of file diff --git a/database/migrations/2021-04-09-075309_create_tables/up.sql b/database/migrations/2021-04-09-075309_create_tables/up.sql new file mode 100644 index 0000000..d8eb9b1 --- /dev/null +++ b/database/migrations/2021-04-09-075309_create_tables/up.sql @@ -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) +); \ No newline at end of file diff --git a/database/src/database.rs b/database/src/database.rs new file mode 100644 index 0000000..5f47dcd --- /dev/null +++ b/database/src/database.rs @@ -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( + &self, + guild_id: u64, + key: &str, + ) -> DatabaseResult> + where + T: FromStr, + { + use guild_settings::dsl; + log::debug!("Retrieving setting '{}' for guild {}", key, guild_id); + let connection = self.pool.get()?; + + let entries: Vec = dsl::guild_settings + .filter(dsl::guild_id.eq(guild_id as i64)) + .filter(dsl::key.eq(key)) + .load::(&connection)?; + log::trace!("Result is {:?}", entries); + + if let Some(first) = entries.first() { + if any::TypeId::of::() == any::TypeId::of::() { + Ok(first + .value + .clone() + .unwrap_or("false".to_string()) + .parse::() + .ok()) + } else { + Ok(first.value.clone().and_then(|v| v.parse::().ok())) + } + } else { + return Ok(None); + } + } + + /// Upserting a guild setting + pub fn set_guild_setting(&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(()) + } +} diff --git a/database/src/error.rs b/database/src/error.rs new file mode 100644 index 0000000..a431aab --- /dev/null +++ b/database/src/error.rs @@ -0,0 +1,21 @@ +use thiserror::Error; + +pub type DatabaseResult = Result; + +#[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), +} diff --git a/database/src/lib.rs b/database/src/lib.rs new file mode 100644 index 0000000..57866b5 --- /dev/null +++ b/database/src/lib.rs @@ -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>; + +embed_migrations!("../database/migrations"); + +fn get_connection() -> DatabaseResult { + dotenv::dotenv()?; + let database_url = env::var("DATABASE_URL").expect("No DATABASE_URL in path"); + log::debug!("Establishing database connection..."); + let manager = ConnectionManager::::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 { + let conn = get_connection()?; + Ok(Database::new(conn)) +} diff --git a/database/src/models.rs b/database/src/models.rs new file mode 100644 index 0000000..aea5738 --- /dev/null +++ b/database/src/models.rs @@ -0,0 +1,16 @@ +use crate::schema::*; + +#[derive(Queryable, Debug)] +pub struct GuildSetting { + pub guild_id: i64, + pub key: String, + pub value: Option, +} + +#[derive(Insertable, Debug)] +#[table_name = "guild_settings"] +pub struct GuildSettingInsert { + pub guild_id: i64, + pub key: String, + pub value: String, +} diff --git a/database/src/schema.rs b/database/src/schema.rs new file mode 100644 index 0000000..d896ee3 --- /dev/null +++ b/database/src/schema.rs @@ -0,0 +1,7 @@ +table! { + guild_settings (guild_id, key) { + guild_id -> Int8, + key -> Varchar, + value -> Nullable, + } +} diff --git a/src/client.rs b/src/client.rs index 17a509e..29a5d86 100644 --- a/src/client.rs +++ b/src/client.rs @@ -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 { .await?; { let mut data = client.data.write().await; - data.insert::(StoreData::new(database)) + data.insert::(StoreData::new()); + data.insert::(database); } Ok(client) diff --git a/src/commands/minecraft/enchantment.rs b/src/commands/minecraft/enchantment.rs index 4b18ccf..163765f 100644 --- a/src/commands/minecraft/enchantment.rs +++ b/src/commands/minecraft/enchantment.rs @@ -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")] diff --git a/src/commands/minecraft/item.rs b/src/commands/minecraft/item.rs index 95af889..489b461 100644 --- a/src/commands/minecraft/item.rs +++ b/src/commands/minecraft/item.rs @@ -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")] diff --git a/src/commands/music/mod.rs b/src/commands/music/mod.rs index 7f53ed9..1bc142d 100644 --- a/src/commands/music/mod.rs +++ b/src/commands/music/mod.rs @@ -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; diff --git a/src/commands/music/play.rs b/src/commands/music/play.rs index 01c684f..51496f7 100644 --- a/src/commands/music/play.rs +++ b/src/commands/music/play.rs @@ -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"); diff --git a/src/commands/settings/get.rs b/src/commands/settings/get.rs index 8b193c2..971a357 100644 --- a/src/commands/settings/get.rs +++ b/src/commands/settings/get.rs @@ -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::().ok() { log::debug!("Displaying guild setting of '{}'", key); - let database_lock = database.lock().await; - let setting = database_lock.get_guild_setting::(&guild.id, &key); + let setting = database.get_guild_setting::(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::(&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::(guild.id.0, &key)? { + Some(value) => kv_pairs.push(format!("`{}` = `{}`", key, value)), + None => kv_pairs.push(format!("`{}` not set", key)), } } msg.channel_id diff --git a/src/commands/settings/set.rs b/src/commands/settings/set.rs index d87b7ca..5d4a7e7 100644 --- a/src/commands/settings/set.rs +++ b/src/commands/settings/set.rs @@ -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::().unwrap(); + let key = args.single::().unwrap().to_lowercase(); + if !GUILD_SETTINGS.contains(&&*key) { + msg.channel_id + .say(ctx, format!("Invalid setting `{}`", key)) + .await?; + return Ok(()); + } let value = args.single::().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?; diff --git a/src/database/guild.rs b/src/database/guild.rs deleted file mode 100644 index 78ef394..0000000 --- a/src/database/guild.rs +++ /dev/null @@ -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]; diff --git a/src/database/mod.rs b/src/database/mod.rs deleted file mode 100644 index 40872b2..0000000 --- a/src/database/mod.rs +++ /dev/null @@ -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(&self, guild_id: &GuildId, key: &str) -> BotResult - 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::(r).unwrap()), - ) - .map_err(BotError::from) - .and_then(|s| { - s.setting_value - .parse::() - .map_err(|_| BotError::from("Failed to parse Setting")) - }) - } - - /// Sets a guild setting and overrides it if it already exists - pub fn set_guild_setting(&self, guild_id: &GuildId, key: &str, value: T) -> BotResult<()> - where - T: ToString + FromStr + Clone + Debug, - { - if self.get_guild_setting::(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 { - 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> { - let data = ctx.data.read().await; - let store = data.get::().unwrap(); - - Arc::clone(&store.database) -} diff --git a/src/database/scripts/create_tables.sql b/src/database/scripts/create_tables.sql deleted file mode 100644 index 48a88e1..0000000 --- a/src/database/scripts/create_tables.sql +++ /dev/null @@ -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 -); \ No newline at end of file diff --git a/src/database/scripts/mod.rs b/src/database/scripts/mod.rs deleted file mode 100644 index f50e475..0000000 --- a/src/database/scripts/mod.rs +++ /dev/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"); diff --git a/src/database/scripts/update_tables.sql b/src/database/scripts/update_tables.sql deleted file mode 100644 index 5b710fe..0000000 --- a/src/database/scripts/update_tables.sql +++ /dev/null @@ -1,2 +0,0 @@ -PRAGMA foreign_keys = false; -DROP TABLE IF EXISTS guilds; \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index d33ee10..d74ecee 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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; diff --git a/src/providers/constants.rs b/src/providers/constants.rs new file mode 100644 index 0000000..a3cbbcb --- /dev/null +++ b/src/providers/constants.rs @@ -0,0 +1,2 @@ +pub const SETTING_AUTOSHUFFLE: &str = "music.autoshuffle"; +pub const GUILD_SETTINGS: &[&str] = &[SETTING_AUTOSHUFFLE]; diff --git a/src/providers/mod.rs b/src/providers/mod.rs index bfb8134..3dbf313 100644 --- a/src/providers/mod.rs +++ b/src/providers/mod.rs @@ -1 +1,2 @@ +pub(crate) mod constants; pub(crate) mod music; diff --git a/src/utils/store.rs b/src/utils/context_data.rs similarity index 62% rename from src/utils/store.rs rename to src/utils/context_data.rs index ed6ab5b..0249597 100644 --- a/src/utils/store.rs +++ b/src/utils/context_data.rs @@ -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>, pub minecraft_data_api: minecraft_data_rs::api::Api, pub music_queues: HashMap>>, 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::() + .expect("Invalid Context setup: Missing database"); + + database.clone() +} diff --git a/src/utils/error.rs b/src/utils/error.rs index 5fb3885..31ce166 100644 --- a/src/utils/error.rs +++ b/src/utils/error.rs @@ -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, diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 9295b30..772523d 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -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(deque: &mut VecDeque) {