From e834b3308ad21d8c437e10f7d536381de5f85ba1 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Mon, 15 May 2023 17:38:13 +0200 Subject: [PATCH 1/5] feat: initial register functionality --- .env | 1 + .gitignore | 1 + Cargo.lock | 69 +++++++++++++++++++ Cargo.toml | 1 + diesel.toml | 8 +++ migrations/.keep | 0 .../2023-05-15-142901_create_users/down.sql | 2 + .../2023-05-15-142901_create_users/up.sql | 7 ++ src/commands/mod.rs | 9 ++- src/commands/users.rs | 45 ++++++++++++ src/db/mod.rs | 2 + src/db/schema.rs | 10 +++ src/db/users.rs | 22 ++++++ src/main.rs | 7 ++ src/models.rs | 0 15 files changed, 183 insertions(+), 1 deletion(-) create mode 100644 .env create mode 100644 diesel.toml create mode 100644 migrations/.keep create mode 100644 migrations/2023-05-15-142901_create_users/down.sql create mode 100644 migrations/2023-05-15-142901_create_users/up.sql create mode 100644 src/commands/users.rs create mode 100644 src/db/mod.rs create mode 100644 src/db/schema.rs create mode 100644 src/db/users.rs create mode 100644 src/models.rs diff --git a/.env b/.env new file mode 100644 index 0000000..3e6e71e --- /dev/null +++ b/.env @@ -0,0 +1 @@ +DATABASE_URL=sqlite://affy.db diff --git a/.gitignore b/.gitignore index b128699..a7e99c9 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,4 @@ rust-project.json # End of https://www.toptal.com/developers/gitignore/api/rust,rust-analyzer +*.db diff --git a/Cargo.lock b/Cargo.lock index 99099f4..c88ad20 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -35,6 +35,7 @@ dependencies = [ "affluences-api", "async-minecraft-ping", "chrono", + "diesel", "poise", "tokio", "uuid", @@ -413,6 +414,28 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "diesel" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72eb77396836a4505da85bae0712fa324b74acfe1876d7c2f7e694ef3d0ee373" +dependencies = [ + "diesel_derives", + "libsqlite3-sys", +] + +[[package]] +name = "diesel_derives" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ad74fdcf086be3d4fdd142f67937678fe60ed431c3b2f08599e7687269410c4" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "digest" version = "0.10.6" @@ -793,6 +816,16 @@ version = "0.2.144" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" +[[package]] +name = "libsqlite3-sys" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afc22eff61b133b115c6e8c74e818c628d6d5e7a502afea6f64dee076dd94326" +dependencies = [ + "pkg-config", + "vcpkg", +] + [[package]] name = "link-cplusplus" version = "1.0.8" @@ -955,6 +988,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkg-config" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" + [[package]] name = "poise" version = "0.5.5" @@ -992,6 +1031,30 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" version = "1.0.56" @@ -1644,6 +1707,12 @@ dependencies = [ "serde", ] +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.4" diff --git a/Cargo.toml b/Cargo.toml index 0a82e38..a4d6e6f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,3 +22,4 @@ chrono = "*" uuid = "*" poise = "0.5.5" async-minecraft-ping = "0.8.0" +diesel = { version = "2.0.4", features = ["sqlite", "returning_clauses_for_sqlite_3_35"] } diff --git a/diesel.toml b/diesel.toml new file mode 100644 index 0000000..88db6ba --- /dev/null +++ b/diesel.toml @@ -0,0 +1,8 @@ +# For documentation on how to configure this file, +# see https://diesel.rs/guides/configuring-diesel-cli + +[print_schema] +file = "src/db/schema.rs" + +[migrations_directory] +dir = "migrations" diff --git a/migrations/.keep b/migrations/.keep new file mode 100644 index 0000000..e69de29 diff --git a/migrations/2023-05-15-142901_create_users/down.sql b/migrations/2023-05-15-142901_create_users/down.sql new file mode 100644 index 0000000..dc3714b --- /dev/null +++ b/migrations/2023-05-15-142901_create_users/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +DROP TABLE users; diff --git a/migrations/2023-05-15-142901_create_users/up.sql b/migrations/2023-05-15-142901_create_users/up.sql new file mode 100644 index 0000000..e5148b3 --- /dev/null +++ b/migrations/2023-05-15-142901_create_users/up.sql @@ -0,0 +1,7 @@ +-- Your SQL goes here +CREATE TABLE users ( + discord_id UNSIGNED BIG INT PRIMARY KEY NOT NULL, + email TEXT UNIQUE NOT NULL, + first_name TEXT NOT NULL, + last_name TEXT NOT NULL +); diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 70a5dea..d0f47ce 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,12 +1,19 @@ mod affluence; mod minecraft; +mod users; use crate::{Context, Data, Error}; type EmbedField = (String, String, bool); pub fn commands() -> Vec> { - vec![help(), affluence::available(), minecraft::ping_mc()] + vec![ + help(), + affluence::available(), + minecraft::ping_mc(), + users::register(), + users::registered(), + ] } /// Show this help menu diff --git a/src/commands/users.rs b/src/commands/users.rs new file mode 100644 index 0000000..df0e7e6 --- /dev/null +++ b/src/commands/users.rs @@ -0,0 +1,45 @@ +use crate::db::users::{user_all, user_insert, User}; +use crate::{Context, Error}; + +#[poise::command(prefix_command, slash_command)] +pub async fn register( + ctx: Context<'_>, + first_name: String, + last_name: String, + email: String, +) -> Result<(), Error> { + let user = User { + discord_id: ctx.author().id.0 as i64, + first_name, + last_name, + email, + }; + + { + let mut conn = ctx.data().conn.lock().unwrap(); + user_insert(&mut conn, &user); + } + + Ok(()) +} + +#[poise::command(prefix_command, slash_command)] +pub async fn registered(ctx: Context<'_>) -> Result<(), Error> { + let users = { + let mut conn = ctx.data().conn.lock().unwrap(); + user_all(&mut conn) + }; + + ctx.send(|f| { + f.embed(|e| { + e.description("Registered users").fields( + users + .into_iter() + .map(|u| (format!("{} {}", u.first_name, u.last_name), u.email, false)), + ) + }) + }) + .await?; + + Ok(()) +} diff --git a/src/db/mod.rs b/src/db/mod.rs new file mode 100644 index 0000000..aa8f28e --- /dev/null +++ b/src/db/mod.rs @@ -0,0 +1,2 @@ +mod schema; +pub mod users; diff --git a/src/db/schema.rs b/src/db/schema.rs new file mode 100644 index 0000000..f50726b --- /dev/null +++ b/src/db/schema.rs @@ -0,0 +1,10 @@ +// @generated automatically by Diesel CLI. + +diesel::table! { + users (discord_id) { + discord_id -> BigInt, + email -> Text, + first_name -> Text, + last_name -> Text, + } +} diff --git a/src/db/users.rs b/src/db/users.rs new file mode 100644 index 0000000..17040b0 --- /dev/null +++ b/src/db/users.rs @@ -0,0 +1,22 @@ +use super::schema::users; +use diesel::prelude::*; +use diesel::sqlite::SqliteConnection; + +#[derive(Queryable, Insertable)] +pub struct User { + pub discord_id: i64, + pub email: String, + pub first_name: String, + pub last_name: String, +} + +pub fn user_insert(conn: &mut SqliteConnection, user: &User) -> User { + diesel::insert_into(users::table) + .values(user) + .get_result(conn) + .expect("fuck") +} + +pub fn user_all(conn: &mut SqliteConnection) -> Vec { + users::table.load::(conn).expect("nou") +} diff --git a/src/main.rs b/src/main.rs index c052b07..e4364d8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,10 @@ mod commands; +mod db; use affluences_api::AffluencesClient; +use diesel::Connection; use poise::serenity_prelude as serenity; +use std::sync::Mutex; use std::{env::var, time::Duration}; // Types used by all command functions @@ -11,6 +14,7 @@ type Context<'a> = poise::Context<'a, Data, Error>; // Custom user data passed to all command functions pub struct Data { client: AffluencesClient, + conn: Mutex, } async fn on_error(error: poise::FrameworkError<'_, Data, Error>) { @@ -91,6 +95,9 @@ async fn main() { poise::builtins::register_globally(ctx, &framework.options().commands).await?; Ok(Data { client: AffluencesClient::new(), + conn: Mutex::new( + diesel::sqlite::SqliteConnection::establish("affy.db").unwrap(), + ), }) }) }) diff --git a/src/models.rs b/src/models.rs new file mode 100644 index 0000000..e69de29 From 303e3ffd4e31e7bad88006a14964504b151ee234 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Tue, 16 May 2023 09:00:12 +0200 Subject: [PATCH 2/5] feat: add database connection pooling --- Cargo.lock | 21 +++++++++++++++++++++ Cargo.toml | 2 +- src/commands/users.rs | 4 ++-- src/main.rs | 14 ++++++++++---- 4 files changed, 34 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c88ad20..c9d2c0a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -422,6 +422,7 @@ checksum = "72eb77396836a4505da85bae0712fa324b74acfe1876d7c2f7e694ef3d0ee373" dependencies = [ "diesel_derives", "libsqlite3-sys", + "r2d2", ] [[package]] @@ -1073,6 +1074,17 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r2d2" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51de85fb3fb6524929c8a2eb85e6b6d363de4e8c48f9e2c2eac4944abc181c93" +dependencies = [ + "log", + "parking_lot", + "scheduled-thread-pool", +] + [[package]] name = "rand" version = "0.8.5" @@ -1231,6 +1243,15 @@ version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" +[[package]] +name = "scheduled-thread-pool" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cbc66816425a074528352f5789333ecff06ca41b36b0b0efdfbb29edc391a19" +dependencies = [ + "parking_lot", +] + [[package]] name = "scopeguard" version = "1.1.0" diff --git a/Cargo.toml b/Cargo.toml index a4d6e6f..1faac2a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,4 +22,4 @@ chrono = "*" uuid = "*" poise = "0.5.5" async-minecraft-ping = "0.8.0" -diesel = { version = "2.0.4", features = ["sqlite", "returning_clauses_for_sqlite_3_35"] } +diesel = { version = "2.0.4", features = ["sqlite", "returning_clauses_for_sqlite_3_35", "r2d2"] } diff --git a/src/commands/users.rs b/src/commands/users.rs index df0e7e6..e164d9a 100644 --- a/src/commands/users.rs +++ b/src/commands/users.rs @@ -16,7 +16,7 @@ pub async fn register( }; { - let mut conn = ctx.data().conn.lock().unwrap(); + let mut conn = ctx.data().pool.get()?; user_insert(&mut conn, &user); } @@ -26,7 +26,7 @@ pub async fn register( #[poise::command(prefix_command, slash_command)] pub async fn registered(ctx: Context<'_>) -> Result<(), Error> { let users = { - let mut conn = ctx.data().conn.lock().unwrap(); + let mut conn = ctx.data().pool.get()?; user_all(&mut conn) }; diff --git a/src/main.rs b/src/main.rs index e4364d8..2bc1ab9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,6 +6,8 @@ use diesel::Connection; use poise::serenity_prelude as serenity; use std::sync::Mutex; use std::{env::var, time::Duration}; +use diesel::r2d2::{ConnectionManager, Pool}; +use diesel::sqlite::SqliteConnection; // Types used by all command functions type Error = Box; @@ -14,7 +16,7 @@ type Context<'a> = poise::Context<'a, Data, Error>; // Custom user data passed to all command functions pub struct Data { client: AffluencesClient, - conn: Mutex, + pool: Pool> } async fn on_error(error: poise::FrameworkError<'_, Data, Error>) { @@ -34,6 +36,12 @@ async fn on_error(error: poise::FrameworkError<'_, Data, Error>) { } } +fn db_connection_pool(url: &str) -> Pool> { + let manager = ConnectionManager::new(url); + + Pool::builder().test_on_check_out(true).build(manager).expect("oops") +} + #[tokio::main] async fn main() { // FrameworkOptions contains all of poise's configuration option in one struct @@ -95,9 +103,7 @@ async fn main() { poise::builtins::register_globally(ctx, &framework.options().commands).await?; Ok(Data { client: AffluencesClient::new(), - conn: Mutex::new( - diesel::sqlite::SqliteConnection::establish("affy.db").unwrap(), - ), + pool: db_connection_pool("affy.db") }) }) }) From 0b10885015ba820f7655ad49d07c7a23d9c8c14e Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Tue, 16 May 2023 15:47:13 +0200 Subject: [PATCH 3/5] feat: starting db abstractions --- .../2023-05-15-142901_create_users/up.sql | 8 ++- src/commands/users.rs | 56 +++++++++------- src/db/mod.rs | 21 ++++++ src/db/schema.rs | 4 +- src/db/users.rs | 64 ++++++++++++++++--- src/main.rs | 10 +-- 6 files changed, 119 insertions(+), 44 deletions(-) diff --git a/migrations/2023-05-15-142901_create_users/up.sql b/migrations/2023-05-15-142901_create_users/up.sql index e5148b3..b1ec088 100644 --- a/migrations/2023-05-15-142901_create_users/up.sql +++ b/migrations/2023-05-15-142901_create_users/up.sql @@ -1,7 +1,11 @@ -- Your SQL goes here CREATE TABLE users ( - discord_id UNSIGNED BIG INT PRIMARY KEY NOT NULL, + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + discord_id UNSIGNED BIG INT NOT NULL, + guild_id UNSIGNED BIG INT NOT NULL, email TEXT UNIQUE NOT NULL, first_name TEXT NOT NULL, - last_name TEXT NOT NULL + last_name TEXT NOT NULL, + + UNIQUE(discord_id, guild_id) ); diff --git a/src/commands/users.rs b/src/commands/users.rs index e164d9a..e692b6a 100644 --- a/src/commands/users.rs +++ b/src/commands/users.rs @@ -1,5 +1,6 @@ -use crate::db::users::{user_all, user_insert, User}; +use crate::db::users::{User, NewUser}; use crate::{Context, Error}; +use diesel::RunQueryDsl; #[poise::command(prefix_command, slash_command)] pub async fn register( @@ -8,16 +9,21 @@ pub async fn register( last_name: String, email: String, ) -> Result<(), Error> { - let user = User { - discord_id: ctx.author().id.0 as i64, - first_name, - last_name, - email, - }; + if let Some(guild_id) = ctx.guild_id() { + let new_user = NewUser { + discord_id: ctx.author().id.0 as i64, + guild_id: guild_id.into(), + first_name, + last_name, + email, + }; - { - let mut conn = ctx.data().pool.get()?; - user_insert(&mut conn, &user); + { + let mut conn = ctx.data().pool.get()?; + new_user.insert(&mut conn); + } + } else { + ctx.say("You have to send this message from a guild.").await?; } Ok(()) @@ -25,21 +31,25 @@ pub async fn register( #[poise::command(prefix_command, slash_command)] pub async fn registered(ctx: Context<'_>) -> Result<(), Error> { - let users = { - let mut conn = ctx.data().pool.get()?; - user_all(&mut conn) - }; + if let Some(guild_id) = ctx.guild_id() { + let users = { + let mut conn = ctx.data().pool.get()?; + User::by_guild_id(guild_id.into()).load(&mut conn)? + }; - ctx.send(|f| { - f.embed(|e| { - e.description("Registered users").fields( - users - .into_iter() - .map(|u| (format!("{} {}", u.first_name, u.last_name), u.email, false)), - ) + ctx.send(|f| { + f.embed(|e| { + e.description("Registered users").fields( + users + .into_iter() + .map(|u| (format!("{} {}", u.first_name, u.last_name), u.email, false)), + ) + }) }) - }) - .await?; + .await?; + } else { + ctx.say("You are not in a guild.").await?; + } Ok(()) } diff --git a/src/db/mod.rs b/src/db/mod.rs index aa8f28e..6c29d3a 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -1,2 +1,23 @@ mod schema; pub mod users; + +use diesel::sqlite::SqliteConnection; +use diesel::connection::SimpleConnection; +use diesel::QueryResult; +use diesel::r2d2::{ConnectionManager, Pool}; + +fn initialize_db(conn: &mut SqliteConnection) -> QueryResult<()> { + // Enable WAL mode and enforce foreign keys + conn.batch_execute("PRAGMA journal_mode = WAL; PRAGMA synchronous = NORMAL; PRAGMA foreign_keys = ON;") +} + +pub fn initialize_pool(url: &str) -> Pool> { + let manager = ConnectionManager::new(url); + + let pool = Pool::builder().test_on_check_out(true).build(manager).expect("oops"); + + let mut conn = pool.get().unwrap(); + initialize_db(&mut conn).unwrap(); + + pool +} diff --git a/src/db/schema.rs b/src/db/schema.rs index f50726b..e43eb3e 100644 --- a/src/db/schema.rs +++ b/src/db/schema.rs @@ -1,8 +1,10 @@ // @generated automatically by Diesel CLI. diesel::table! { - users (discord_id) { + users (id) { + id -> Integer, discord_id -> BigInt, + guild_id -> BigInt, email -> Text, first_name -> Text, last_name -> Text, diff --git a/src/db/users.rs b/src/db/users.rs index 17040b0..357ff3b 100644 --- a/src/db/users.rs +++ b/src/db/users.rs @@ -1,22 +1,68 @@ -use super::schema::users; +use super::schema::users::{self, dsl::*}; use diesel::prelude::*; use diesel::sqlite::SqliteConnection; +use diesel::dsl::{AsSelect, Select}; +use diesel::sqlite::Sqlite; +use diesel::dsl::Eq; +use diesel::helper_types::Filter; +use diesel::sql_types::BigInt; +use diesel::expression::AsExpression; -#[derive(Queryable, Insertable)] +#[derive(Queryable, Selectable)] +#[diesel(table_name = users)] pub struct User { + pub id: i32, pub discord_id: i64, + pub guild_id: i64, pub email: String, pub first_name: String, pub last_name: String, } -pub fn user_insert(conn: &mut SqliteConnection, user: &User) -> User { - diesel::insert_into(users::table) - .values(user) - .get_result(conn) - .expect("fuck") +#[derive(Insertable)] +#[diesel(table_name = users)] +pub struct NewUser { + pub discord_id: i64, + pub guild_id: i64, + pub email: String, + pub first_name: String, + pub last_name: String, } -pub fn user_all(conn: &mut SqliteConnection) -> Vec { - users::table.load::(conn).expect("nou") +type All = Select>; +type WithGuild = Eq; +type ByGuild = Filter>; + +impl User { + pub fn all() -> All { + users::table.select(User::as_select()) + } + + // pub fn by_guild(guild_id_: T) -> ByGuild + // where T: AsExpression + // { + // Self::all().filter(guild_id.eq(guild_id_)) + // } + + pub fn by_guild_id(guild_id_: i64) -> ByGuild + { + Self::all().filter(guild_id.eq(guild_id_)) + } + + pub fn get(conn: &mut SqliteConnection, guild_id_: i64, discord_id_: i64) -> Result { + Self::all().filter(guild_id.eq(guild_id_)).filter(discord_id.eq(discord_id_)).first(conn) + } + + pub fn get_by_id(conn: &mut SqliteConnection, id_: i32) -> Result { + Self::all().filter(id.eq(id_)).first(conn) + } +} + +impl NewUser { + pub fn insert(&self, conn: &mut SqliteConnection) -> User { + diesel::insert_into(users::table) + .values(self) + .get_result(conn) + .expect("fuck") + } } diff --git a/src/main.rs b/src/main.rs index 2bc1ab9..c0a9ae7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,9 +2,7 @@ mod commands; mod db; use affluences_api::AffluencesClient; -use diesel::Connection; use poise::serenity_prelude as serenity; -use std::sync::Mutex; use std::{env::var, time::Duration}; use diesel::r2d2::{ConnectionManager, Pool}; use diesel::sqlite::SqliteConnection; @@ -36,12 +34,6 @@ async fn on_error(error: poise::FrameworkError<'_, Data, Error>) { } } -fn db_connection_pool(url: &str) -> Pool> { - let manager = ConnectionManager::new(url); - - Pool::builder().test_on_check_out(true).build(manager).expect("oops") -} - #[tokio::main] async fn main() { // FrameworkOptions contains all of poise's configuration option in one struct @@ -103,7 +95,7 @@ async fn main() { poise::builtins::register_globally(ctx, &framework.options().commands).await?; Ok(Data { client: AffluencesClient::new(), - pool: db_connection_pool("affy.db") + pool: db::initialize_pool("affy.db") }) }) }) From 04e268a17cfc481e5d1f729814dfb21f90864c21 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Tue, 16 May 2023 16:25:33 +0200 Subject: [PATCH 4/5] feat: database migrations --- .gitignore | 2 +- Cargo.lock | 42 ++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + migrations/.keep | 0 src/build.rs | 3 +++ src/commands/users.rs | 7 +++++-- src/db/mod.rs | 43 +++++++++++++++++++++++++++++++++---------- src/db/users.rs | 28 +++++++++++++++++----------- src/main.rs | 10 ++++++---- 9 files changed, 108 insertions(+), 28 deletions(-) delete mode 100644 migrations/.keep create mode 100644 src/build.rs diff --git a/.gitignore b/.gitignore index a7e99c9..ac95708 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,4 @@ rust-project.json # End of https://www.toptal.com/developers/gitignore/api/rust,rust-analyzer -*.db +*.db* diff --git a/Cargo.lock b/Cargo.lock index c9d2c0a..b8388a2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -36,6 +36,7 @@ dependencies = [ "async-minecraft-ping", "chrono", "diesel", + "diesel_migrations", "poise", "tokio", "uuid", @@ -437,6 +438,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "diesel_migrations" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9ae22beef5e9d6fab9225ddb073c1c6c1a7a6ded5019d5da11d1e5c5adc34e2" +dependencies = [ + "diesel", + "migrations_internals", + "migrations_macros", +] + [[package]] name = "digest" version = "0.10.6" @@ -867,6 +879,27 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +[[package]] +name = "migrations_internals" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c493c09323068c01e54c685f7da41a9ccf9219735c3766fbfd6099806ea08fbc" +dependencies = [ + "serde", + "toml", +] + +[[package]] +name = "migrations_macros" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a8ff27a350511de30cdabb77147501c36ef02e0451d957abea2f30caffb2b58" +dependencies = [ + "migrations_internals", + "proc-macro2", + "quote", +] + [[package]] name = "mime" version = "0.3.17" @@ -1575,6 +1608,15 @@ dependencies = [ "tracing", ] +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + [[package]] name = "tower-service" version = "0.3.2" diff --git a/Cargo.toml b/Cargo.toml index 1faac2a..cde15dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,3 +23,4 @@ uuid = "*" poise = "0.5.5" async-minecraft-ping = "0.8.0" diesel = { version = "2.0.4", features = ["sqlite", "returning_clauses_for_sqlite_3_35", "r2d2"] } +diesel_migrations = { version = "2.0.0", features = [ "sqlite" ] } diff --git a/migrations/.keep b/migrations/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/src/build.rs b/src/build.rs new file mode 100644 index 0000000..508b53c --- /dev/null +++ b/src/build.rs @@ -0,0 +1,3 @@ +fn main() { + println!("cargo:rerun-if-changed=migrations"); +} diff --git a/src/commands/users.rs b/src/commands/users.rs index e692b6a..da968f6 100644 --- a/src/commands/users.rs +++ b/src/commands/users.rs @@ -1,4 +1,4 @@ -use crate::db::users::{User, NewUser}; +use crate::db::users::{NewUser, User}; use crate::{Context, Error}; use diesel::RunQueryDsl; @@ -22,8 +22,11 @@ pub async fn register( let mut conn = ctx.data().pool.get()?; new_user.insert(&mut conn); } + + ctx.say("You have been registered.").await?; } else { - ctx.say("You have to send this message from a guild.").await?; + ctx.say("You have to send this message from a guild.") + .await?; } Ok(()) diff --git a/src/db/mod.rs b/src/db/mod.rs index 6c29d3a..1542bea 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -1,23 +1,46 @@ mod schema; pub mod users; -use diesel::sqlite::SqliteConnection; use diesel::connection::SimpleConnection; -use diesel::QueryResult; use diesel::r2d2::{ConnectionManager, Pool}; +use diesel::sqlite::{Sqlite, SqliteConnection}; +use std::error::Error; -fn initialize_db(conn: &mut SqliteConnection) -> QueryResult<()> { - // Enable WAL mode and enforce foreign keys - conn.batch_execute("PRAGMA journal_mode = WAL; PRAGMA synchronous = NORMAL; PRAGMA foreign_keys = ON;") +use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness}; +pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!("migrations"); + +type DbError = Box; + +fn run_migrations(connection: &mut impl MigrationHarness) -> Result<(), DbError> { + // This will run the necessary migrations. + // + // See the documentation for `MigrationHarness` for + // all available methods. + connection.run_pending_migrations(MIGRATIONS)?; + + Ok(()) } -pub fn initialize_pool(url: &str) -> Pool> { +fn initialize_db(conn: &mut SqliteConnection) -> Result<(), DbError> { + // Enable WAL mode and enforce foreign keys + conn.batch_execute( + "PRAGMA journal_mode = WAL; PRAGMA synchronous = NORMAL; PRAGMA foreign_keys = ON;", + )?; + run_migrations(conn)?; + + Ok(()) +} + +pub fn initialize_pool(url: &str) -> Result>, DbError> { let manager = ConnectionManager::new(url); - let pool = Pool::builder().test_on_check_out(true).build(manager).expect("oops"); + let pool = Pool::builder() + .test_on_check_out(true) + .build(manager) + .expect("oops"); - let mut conn = pool.get().unwrap(); - initialize_db(&mut conn).unwrap(); + let mut conn = pool.get()?; + initialize_db(&mut conn)?; - pool + Ok(pool) } diff --git a/src/db/users.rs b/src/db/users.rs index 357ff3b..09360ee 100644 --- a/src/db/users.rs +++ b/src/db/users.rs @@ -1,12 +1,12 @@ use super::schema::users::{self, dsl::*}; -use diesel::prelude::*; -use diesel::sqlite::SqliteConnection; -use diesel::dsl::{AsSelect, Select}; -use diesel::sqlite::Sqlite; use diesel::dsl::Eq; -use diesel::helper_types::Filter; -use diesel::sql_types::BigInt; +use diesel::dsl::{AsSelect, Select}; use diesel::expression::AsExpression; +use diesel::helper_types::Filter; +use diesel::prelude::*; +use diesel::sql_types::BigInt; +use diesel::sqlite::Sqlite; +use diesel::sqlite::SqliteConnection; #[derive(Queryable, Selectable)] #[diesel(table_name = users)] @@ -44,13 +44,19 @@ impl User { // Self::all().filter(guild_id.eq(guild_id_)) // } - pub fn by_guild_id(guild_id_: i64) -> ByGuild - { + pub fn by_guild_id(guild_id_: i64) -> ByGuild { Self::all().filter(guild_id.eq(guild_id_)) } - - pub fn get(conn: &mut SqliteConnection, guild_id_: i64, discord_id_: i64) -> Result { - Self::all().filter(guild_id.eq(guild_id_)).filter(discord_id.eq(discord_id_)).first(conn) + + pub fn get( + conn: &mut SqliteConnection, + guild_id_: i64, + discord_id_: i64, + ) -> Result { + Self::all() + .filter(guild_id.eq(guild_id_)) + .filter(discord_id.eq(discord_id_)) + .first(conn) } pub fn get_by_id(conn: &mut SqliteConnection, id_: i32) -> Result { diff --git a/src/main.rs b/src/main.rs index c0a9ae7..c1457cf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,10 +2,10 @@ mod commands; mod db; use affluences_api::AffluencesClient; -use poise::serenity_prelude as serenity; -use std::{env::var, time::Duration}; use diesel::r2d2::{ConnectionManager, Pool}; use diesel::sqlite::SqliteConnection; +use poise::serenity_prelude as serenity; +use std::{env::var, time::Duration}; // Types used by all command functions type Error = Box; @@ -14,7 +14,7 @@ type Context<'a> = poise::Context<'a, Data, Error>; // Custom user data passed to all command functions pub struct Data { client: AffluencesClient, - pool: Pool> + pool: Pool>, } async fn on_error(error: poise::FrameworkError<'_, Data, Error>) { @@ -84,6 +84,8 @@ async fn main() { ..Default::default() }; + let pool = db::initialize_pool("affy.db").unwrap(); + poise::Framework::builder() .token( var("DISCORD_TOKEN") @@ -95,7 +97,7 @@ async fn main() { poise::builtins::register_globally(ctx, &framework.options().commands).await?; Ok(Data { client: AffluencesClient::new(), - pool: db::initialize_pool("affy.db") + pool, }) }) }) From 53aeb2339f8a2035543fb0f8be214d865c3fc7bb Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Tue, 16 May 2023 16:52:59 +0200 Subject: [PATCH 5/5] feat: bit more robust register --- src/commands/users.rs | 25 +++++++++++++++++-------- src/db/mod.rs | 5 +---- src/db/users.rs | 21 +++++++++++++-------- src/main.rs | 2 +- 4 files changed, 32 insertions(+), 21 deletions(-) diff --git a/src/commands/users.rs b/src/commands/users.rs index da968f6..f552787 100644 --- a/src/commands/users.rs +++ b/src/commands/users.rs @@ -10,17 +10,26 @@ pub async fn register( email: String, ) -> Result<(), Error> { if let Some(guild_id) = ctx.guild_id() { - let new_user = NewUser { - discord_id: ctx.author().id.0 as i64, - guild_id: guild_id.into(), - first_name, - last_name, - email, - }; + let discord_id = ctx.author().id.0 as i64; { let mut conn = ctx.data().pool.get()?; - new_user.insert(&mut conn); + + if User::get(&mut conn, guild_id.into(), discord_id)?.is_some() { + ctx.say("You've already been registered.").await?; + + return Ok(()); + } + + let new_user = NewUser { + discord_id, + guild_id: guild_id.into(), + first_name, + last_name, + email, + }; + + new_user.insert(&mut conn)?; } ctx.say("You have been registered.").await?; diff --git a/src/db/mod.rs b/src/db/mod.rs index 1542bea..578506e 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -34,10 +34,7 @@ fn initialize_db(conn: &mut SqliteConnection) -> Result<(), DbError> { pub fn initialize_pool(url: &str) -> Result>, DbError> { let manager = ConnectionManager::new(url); - let pool = Pool::builder() - .test_on_check_out(true) - .build(manager) - .expect("oops"); + let pool = Pool::builder().test_on_check_out(true).build(manager)?; let mut conn = pool.get()?; initialize_db(&mut conn)?; diff --git a/src/db/users.rs b/src/db/users.rs index 09360ee..e87f0b9 100644 --- a/src/db/users.rs +++ b/src/db/users.rs @@ -1,14 +1,12 @@ use super::schema::users::{self, dsl::*}; use diesel::dsl::Eq; use diesel::dsl::{AsSelect, Select}; -use diesel::expression::AsExpression; use diesel::helper_types::Filter; use diesel::prelude::*; -use diesel::sql_types::BigInt; use diesel::sqlite::Sqlite; use diesel::sqlite::SqliteConnection; -#[derive(Queryable, Selectable)] +#[derive(Queryable, Selectable, AsChangeset)] #[diesel(table_name = users)] pub struct User { pub id: i32, @@ -52,23 +50,30 @@ impl User { conn: &mut SqliteConnection, guild_id_: i64, discord_id_: i64, - ) -> Result { + ) -> Result, diesel::result::Error> { Self::all() .filter(guild_id.eq(guild_id_)) .filter(discord_id.eq(discord_id_)) .first(conn) + .optional() } - pub fn get_by_id(conn: &mut SqliteConnection, id_: i32) -> Result { - Self::all().filter(id.eq(id_)).first(conn) + pub fn get_by_id( + conn: &mut SqliteConnection, + id_: i32, + ) -> Result, diesel::result::Error> { + Self::all().find(id_).first(conn).optional() + } + + pub fn update(&self, conn: &mut SqliteConnection) -> Result { + diesel::update(users::table).set(self).execute(conn) } } impl NewUser { - pub fn insert(&self, conn: &mut SqliteConnection) -> User { + pub fn insert(&self, conn: &mut SqliteConnection) -> Result { diesel::insert_into(users::table) .values(self) .get_result(conn) - .expect("fuck") } } diff --git a/src/main.rs b/src/main.rs index c1457cf..6328584 100644 --- a/src/main.rs +++ b/src/main.rs @@ -84,7 +84,7 @@ async fn main() { ..Default::default() }; - let pool = db::initialize_pool("affy.db").unwrap(); + let pool = db::initialize_pool("affy.db").expect("Failed to initialize database."); poise::Framework::builder() .token(