Merge branch 'user-register' into dev
Some checks failed
ci/woodpecker/push/lint Pipeline was successful
ci/woodpecker/push/clippy Pipeline failed
ci/woodpecker/push/build Pipeline was successful

This commit is contained in:
Jef Roosens 2023-05-17 08:28:09 +02:00
commit fe171fdc8e
Signed by: Jef Roosens
GPG key ID: B75D4F293C7052DB
15 changed files with 376 additions and 1 deletions

3
src/build.rs Normal file
View file

@ -0,0 +1,3 @@
fn main() {
println!("cargo:rerun-if-changed=migrations");
}

View file

@ -1,12 +1,19 @@
mod affluence;
mod minecraft;
mod users;
use crate::{Context, Data, Error};
type EmbedField = (String, String, bool);
pub fn commands() -> Vec<poise::structs::Command<Data, Error>> {
vec![affluence::available(), minecraft::mc(), help()]
vec![
help(),
affluence::available(),
minecraft::mc(),
users::register(),
users::registered(),
]
}
/// Show this help menu

67
src/commands/users.rs Normal file
View file

@ -0,0 +1,67 @@
use crate::db::users::{NewUser, User};
use crate::{Context, Error};
use diesel::RunQueryDsl;
#[poise::command(prefix_command, slash_command)]
pub async fn register(
ctx: Context<'_>,
first_name: String,
last_name: String,
email: String,
) -> Result<(), Error> {
if let Some(guild_id) = ctx.guild_id() {
let discord_id = ctx.author().id.0 as i64;
{
let mut conn = ctx.data().pool.get()?;
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?;
} else {
ctx.say("You have to send this message from a guild.")
.await?;
}
Ok(())
}
#[poise::command(prefix_command, slash_command)]
pub async fn registered(ctx: Context<'_>) -> Result<(), Error> {
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)),
)
})
})
.await?;
} else {
ctx.say("You are not in a guild.").await?;
}
Ok(())
}

43
src/db/mod.rs Normal file
View file

@ -0,0 +1,43 @@
mod schema;
pub mod users;
use diesel::connection::SimpleConnection;
use diesel::r2d2::{ConnectionManager, Pool};
use diesel::sqlite::{Sqlite, SqliteConnection};
use std::error::Error;
use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness};
pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!("migrations");
type DbError = Box<dyn Error + Send + Sync + 'static>;
fn run_migrations(connection: &mut impl MigrationHarness<Sqlite>) -> Result<(), DbError> {
// This will run the necessary migrations.
//
// See the documentation for `MigrationHarness` for
// all available methods.
connection.run_pending_migrations(MIGRATIONS)?;
Ok(())
}
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<Pool<ConnectionManager<SqliteConnection>>, DbError> {
let manager = ConnectionManager::new(url);
let pool = Pool::builder().test_on_check_out(true).build(manager)?;
let mut conn = pool.get()?;
initialize_db(&mut conn)?;
Ok(pool)
}

12
src/db/schema.rs Normal file
View file

@ -0,0 +1,12 @@
// @generated automatically by Diesel CLI.
diesel::table! {
users (id) {
id -> Integer,
discord_id -> BigInt,
guild_id -> BigInt,
email -> Text,
first_name -> Text,
last_name -> Text,
}
}

79
src/db/users.rs Normal file
View file

@ -0,0 +1,79 @@
use super::schema::users::{self, dsl::*};
use diesel::dsl::Eq;
use diesel::dsl::{AsSelect, Select};
use diesel::helper_types::Filter;
use diesel::prelude::*;
use diesel::sqlite::Sqlite;
use diesel::sqlite::SqliteConnection;
#[derive(Queryable, Selectable, AsChangeset)]
#[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,
}
#[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,
}
type All = Select<users::table, AsSelect<User, Sqlite>>;
type WithGuild<T> = Eq<guild_id, T>;
type ByGuild<T> = Filter<All, WithGuild<T>>;
impl User {
pub fn all() -> All {
users::table.select(User::as_select())
}
// pub fn by_guild<T>(guild_id_: T) -> ByGuild<T>
// where T: AsExpression<BigInt>
// {
// Self::all().filter(guild_id.eq(guild_id_))
// }
pub fn by_guild_id(guild_id_: i64) -> ByGuild<i64> {
Self::all().filter(guild_id.eq(guild_id_))
}
pub fn get(
conn: &mut SqliteConnection,
guild_id_: i64,
discord_id_: i64,
) -> Result<Option<User>, 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<Option<User>, diesel::result::Error> {
Self::all().find(id_).first(conn).optional()
}
pub fn update(&self, conn: &mut SqliteConnection) -> Result<usize, diesel::result::Error> {
diesel::update(users::table).set(self).execute(conn)
}
}
impl NewUser {
pub fn insert(&self, conn: &mut SqliteConnection) -> Result<User, diesel::result::Error> {
diesel::insert_into(users::table)
.values(self)
.get_result(conn)
}
}

View file

@ -1,6 +1,9 @@
mod commands;
mod db;
use affluences_api::AffluencesClient;
use diesel::r2d2::{ConnectionManager, Pool};
use diesel::sqlite::SqliteConnection;
use poise::serenity_prelude as serenity;
use std::{env::var, time::Duration};
@ -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,
pool: Pool<ConnectionManager<SqliteConnection>>,
}
async fn on_error(error: poise::FrameworkError<'_, Data, Error>) {
@ -80,6 +84,8 @@ async fn main() {
..Default::default()
};
let pool = db::initialize_pool("affy.db").expect("Failed to initialize database.");
poise::Framework::builder()
.token(
var("DISCORD_TOKEN")
@ -91,6 +97,7 @@ async fn main() {
poise::builtins::register_globally(ctx, &framework.options().commands).await?;
Ok(Data {
client: AffluencesClient::new(),
pool,
})
})
})

0
src/models.rs Normal file
View file