From 591c61b009f2b22339dabd9e38642eb185bf4e42 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Sun, 17 Sep 2023 00:14:50 +0100 Subject: [PATCH] feat: now using seperate tables for key info, joined when needed --- src/bin/update_data.rs | 200 +++++++++++++++++-------------------- src/bin/update_users.rs | 21 ++-- src/commands/link_email.rs | 64 ++++++------ src/commands/mod.rs | 2 +- src/lib.rs | 122 ++++++++++++++-------- src/main.rs | 14 +-- 6 files changed, 225 insertions(+), 198 deletions(-) diff --git a/src/bin/update_data.rs b/src/bin/update_data.rs index d4eb3c8..315ced6 100644 --- a/src/bin/update_data.rs +++ b/src/bin/update_data.rs @@ -1,4 +1,4 @@ -use skynet_discord_bot::{db_init, get_config, get_server_config_bulk, Accounts, Config, Servers}; +use skynet_discord_bot::{db_init, get_config, get_server_config_bulk, Config, ServerMembers, Servers, Wolves}; use serde::Deserialize; use serenity::model::id::GuildId; @@ -38,66 +38,116 @@ struct RecordCSV { expiry: String, } -impl From for Accounts { - fn from(input: RecordCSV) -> Self { +impl From<&RecordCSV> for Wolves { + fn from(input: &RecordCSV) -> Self { Self { - server: Default::default(), - id_wolves: "".to_string(), - id_member: input.mem_id, - email: input.email, - expiry: input.expiry, + id_wolves: input.mem_id.to_owned(), + email: input.email.to_owned(), discord: None, minecraft: None, } } } -fn get_csv(config: &Config) -> Result, Box> { - let mut records: Vec = vec![]; +impl From<&RecordCSV> for ServerMembers { + fn from(input: &RecordCSV) -> Self { + Self { + server: Default::default(), + id_wolves: input.mem_id.to_owned(), + expiry: input.expiry.to_owned(), + } + } +} + +struct Csv { + wolves: Wolves, + server_members: ServerMembers, +} + +fn get_csv(config: &Config) -> Result, Box> { + let mut result = vec![]; let csv = format!("{}/{}", &config.home, &config.csv); if let Ok(mut rdr) = csv::Reader::from_path(csv) { - for result in rdr.deserialize() { + for r in rdr.deserialize() { // Notice that we need to provide a type hint for automatic // deserialization. - let record: RecordCSV = result?; + let record: RecordCSV = r?; if record.mem_id.is_empty() { continue; } - records.push(Accounts::from(record)); + + result.push(Csv { + wolves: Wolves::from(&record), + server_members: ServerMembers::from(&record), + }); } } - Ok(records) + Ok(result) } -async fn add_users_wolves_csv(db: &Pool, server: &GuildId, user: &Accounts) { - let existing = match sqlx::query_as::<_, Accounts>( - r#" - SELECT * - FROM accounts - WHERE server = ? AND id_member = ? - "#, +async fn add_users_wolves_csv(db: &Pool, server: &GuildId, user: &Csv) { + match sqlx::query_as::<_, Wolves>( + " + INSERT OR REPLACE INTO wolves (id_wolves, email) + VALUES (?1, ?2) + ", ) - .bind(*server.as_u64() as i64) - .bind(&user.id_member) - .fetch_one(db) + .bind(&user.wolves.id_wolves) + .bind(&user.wolves.email) + .fetch_optional(db) .await { - Ok(acc) => acc.id_wolves, - Err(_) => String::new(), - }; + Ok(_) => {} + Err(e) => { + println!("Failure to insert into {} {:?}", server.as_u64(), user.wolves); + println!("{:?}", e); + } + } - match sqlx::query_as::<_, Accounts>( + match sqlx::query_as::<_, ServerMembers>( " - INSERT OR REPLACE INTO accounts (server, id_wolves, id_member, email, expiry) - VALUES (?1, ?2, ?3, ?4, ?5) + INSERT OR REPLACE INTO server_members (server, id_wolves, expiry) + VALUES (?1, ?2, ?3) ", ) .bind(*server.as_u64() as i64) - .bind(&existing) + .bind(&user.server_members.id_wolves) + .bind(&user.server_members.expiry) + .fetch_optional(db) + .await + { + Ok(_) => {} + Err(e) => { + println!("Failure to insert into {} {:?}", server.as_u64(), user.server_members); + println!("{:?}", e); + } + } +} + +#[derive(Debug, Deserialize)] +pub struct SkynetResult { + discord: String, + id_member: String, +} +async fn get_skynet(db: &Pool, config: &Config) { + let url = format!("{}/ldap/discord?auth={}", &config.ldap_api, &config.auth); + if let Ok(result) = surf::get(url).recv_json::>().await { + for user in result { + add_users_skynet(db, &config.skynet_server, &user).await; + } + } +} +async fn add_users_skynet(db: &Pool, server: &GuildId, user: &SkynetResult) { + match sqlx::query_as::<_, Wolves>( + " + UPDATE wolves + SET discord = ? + WHERE id_wolves = ? + ", + ) + .bind(&user.discord) .bind(&user.id_member) - .bind(&user.email) - .bind(&user.expiry) .fetch_optional(db) .await { @@ -109,69 +159,9 @@ async fn add_users_wolves_csv(db: &Pool, server: &GuildId, user: &Accoun } } -#[derive(Debug, Deserialize)] -pub struct SkynetResult { - discord: String, - id_wolves: String, - id_member: String, -} -async fn get_skynet(db: &Pool, config: &Config) { - let url = format!("{}/ldap/discord?auth={}", &config.ldap_api, &config.auth); - if let Ok(result) = surf::get(url).recv_json::>().await { - for user in result { - add_users_skynet(db, &config.skynet_server, &user).await; - } - } -} -async fn add_users_skynet(db: &Pool, server: &GuildId, user: &SkynetResult) { - if !user.id_wolves.is_empty() { - match sqlx::query_as::<_, Accounts>( - " - UPDATE accounts - SET discord = ? - WHERE server = ? AND id_wolves = ? - ", - ) - .bind(&user.discord) - .bind(*server.as_u64() as i64) - .bind(&user.id_wolves) - .fetch_optional(db) - .await - { - Ok(_) => {} - Err(e) => { - println!("Failure to insert into {} {:?}", server.as_u64(), user); - println!("{:?}", e); - } - } - } - if !user.id_member.is_empty() { - match sqlx::query_as::<_, Accounts>( - " - UPDATE accounts - SET discord = ? - WHERE server = ? AND id_member = ? - ", - ) - .bind(&user.discord) - .bind(*server.as_u64() as i64) - .bind(&user.id_member) - .fetch_optional(db) - .await - { - Ok(_) => {} - Err(e) => { - println!("Failure to insert into {} {:?}", server.as_u64(), user); - println!("{:?}", e); - } - } - } -} - #[derive(Debug, Deserialize)] struct WolvesResult { pub id_wolves: String, - pub id_member: String, pub email: String, pub expiry: String, } @@ -186,7 +176,6 @@ async fn get_wolves(db: &Pool) { // get the data here let result: Vec = vec![WolvesResult { id_wolves: "12345".to_string(), - id_member: "166425".to_string(), email: "ul.wolves2@brendan.ie".to_string(), expiry: "2024-08-31".to_string(), }]; @@ -198,43 +187,40 @@ async fn get_wolves(db: &Pool) { } async fn add_users_wolves(db: &Pool, server: &GuildId, user: &WolvesResult) { - match sqlx::query_as::<_, Accounts>( + // expiry + match sqlx::query_as::<_, Wolves>( " - UPDATE accounts - SET id_wolves = ? - WHERE server = ? AND id_member = ? + INSERT OR REPLACE INTO wolves (id_wolves, email) + VALUES (?1, ?2) ", ) .bind(&user.id_wolves) - .bind(*server.as_u64() as i64) - .bind(&user.id_member) + .bind(&user.email) .fetch_optional(db) .await { Ok(_) => {} Err(e) => { - println!("Failure to update into {} {:?}", server.as_u64(), user); + println!("Failure to insert into Wolves {} {:?}", server.as_u64(), user); println!("{:?}", e); } } - match sqlx::query_as::<_, Accounts>( + match sqlx::query_as::<_, ServerMembers>( " - INSERT OR REPLACE INTO accounts (server, id_wolves, id_member, email, expiry) - VALUES (?1, ?2, ?3, ?4, ?5) + INSERT OR REPLACE INTO server_members (server, id_wolves, expiry) + VALUES (?1, ?2, ?3) ", ) .bind(*server.as_u64() as i64) .bind(&user.id_wolves) - .bind(&user.id_member) - .bind(&user.email) .bind(&user.expiry) .fetch_optional(db) .await { Ok(_) => {} Err(e) => { - println!("Failure to insert into {} {:?}", server.as_u64(), user); + println!("Failure to insert into ServerMembers {} {:?}", server.as_u64(), user); println!("{:?}", e); } } diff --git a/src/bin/update_users.rs b/src/bin/update_users.rs index e4c6c28..b0098d7 100644 --- a/src/bin/update_users.rs +++ b/src/bin/update_users.rs @@ -7,7 +7,7 @@ use serenity::{ }, Client, }; -use skynet_discord_bot::{db_init, get_config, get_now_iso, get_server_config_bulk, Accounts, Config, DataBase, Servers}; +use skynet_discord_bot::{db_init, get_config, get_now_iso, get_server_config_bulk, Config, DataBase, Servers, Wolves}; use sqlx::{Pool, Sqlite}; use std::{process, sync::Arc}; use tokio::sync::RwLock; @@ -131,13 +131,18 @@ async fn bulk_check(ctx: Arc) { } } -async fn get_server_member_bulk(db: &Pool, server: &GuildId) -> Vec { - sqlx::query_as::<_, Accounts>( +async fn get_server_member_bulk(db: &Pool, server: &GuildId) -> Vec { + sqlx::query_as::<_, Wolves>( r#" - SELECT * - FROM accounts - WHERE server = ? AND discord IS NOT NULL AND expiry > ? - "#, + SELECT * + FROM server_members + JOIN wolves ON server_members.id_wolves = wolves.id_wolves + WHERE ( + server = ? + AND discord IS NOT NULL + AND expiry > ? + ) + "#, ) .bind(*server.as_u64() as i64) .bind(get_now_iso(true)) @@ -147,7 +152,7 @@ async fn get_server_member_bulk(db: &Pool, server: &GuildId) -> Vec, server: &GuildId, past: i64, current: i64) { - match sqlx::query_as::<_, Accounts>( + match sqlx::query_as::<_, Wolves>( " UPDATE servers SET member_past = ?, member_current = ? diff --git a/src/commands/link_email.rs b/src/commands/link_email.rs index 7e275e9..85e3801 100644 --- a/src/commands/link_email.rs +++ b/src/commands/link_email.rs @@ -1,17 +1,11 @@ use serenity::builder::CreateApplicationCommand; use serenity::client::Context; -use serenity::model::application::interaction::Interaction; use serenity::model::application::interaction::application_command::ApplicationCommandInteraction; use serenity::model::id::GuildId; use serenity::model::prelude::command::CommandOptionType; -use serenity::model::prelude::interaction::application_command::{ - CommandDataOption, - CommandDataOptionValue, -}; -use serenity::model::user::User; +use serenity::model::prelude::interaction::application_command::CommandDataOptionValue; +use skynet_discord_bot::{get_now_iso, DataBase, Wolves}; use sqlx::{Pool, Sqlite}; -use skynet_discord_bot::DataBase; - pub async fn run(options: &ApplicationCommandInteraction, ctx: &Context) -> String { let db_lock = { @@ -19,11 +13,15 @@ pub async fn run(options: &ApplicationCommandInteraction, ctx: &Context) -> Stri data_read.get::().expect("Expected Config in TypeMap.").clone() }; let db = db_lock.read().await; - - - - - let option = options.data.options.get(0).expect("Expected email option").resolved.as_ref().expect("Expected email object"); + + let option = options + .data + .options + .get(0) + .expect("Expected email option") + .resolved + .as_ref() + .expect("Expected email object"); if let CommandDataOptionValue::String(email) = option { format!("Email is {}, user is {} {:?}", email, options.user.name, options.guild_id) @@ -33,26 +31,28 @@ pub async fn run(options: &ApplicationCommandInteraction, ctx: &Context) -> Stri } pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { - command.name("link").description("Set Wolves Email").create_option(|option| { - option - .name("email") - .description("UL Wolves Email") - .kind(CommandOptionType::String) - .required(true) - }) + command + .name("link") + .description("Set Wolves Email") + .create_option(|option| option.name("email").description("UL Wolves Email").kind(CommandOptionType::String).required(true)) } -async fn get_server_member(db: &Pool, server: &GuildId) -> Vec { - sqlx::query_as::<_, Accounts>( +async fn get_server_member_bulk(db: &Pool, server: &GuildId) -> Vec { + sqlx::query_as::<_, Wolves>( r#" - SELECT * - FROM wolves - WHERE server = ? AND discord IS NOT NULL AND expiry > ? - "#, + SELECT * + FROM server_members + JOIN wolves ON server_members.id_wolves = wolves.id_wolves + WHERE ( + server = ? + AND discord IS NOT NULL + AND expiry > ? + ) + "#, ) - .bind(*server.as_u64() as i64) - .bind(get_now_iso(true)) - .fetch_all(db) - .await - .unwrap_or_default() -} \ No newline at end of file + .bind(*server.as_u64() as i64) + .bind(get_now_iso(true)) + .fetch_all(db) + .await + .unwrap_or_default() +} diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 291a2ed..1ab5243 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1 +1 @@ -pub mod link_email; \ No newline at end of file +pub mod link_email; diff --git a/src/lib.rs b/src/lib.rs index 4a33b54..5777f39 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -109,17 +109,12 @@ fn str_to_num(x: &str) -> T { } #[derive(Debug, Clone, Deserialize, Serialize)] -pub struct Accounts { +pub struct ServerMembers { pub server: GuildId, pub id_wolves: String, - pub id_member: String, - pub email: String, pub expiry: String, - pub discord: Option, - pub minecraft: Option, } - -impl<'r> FromRow<'r, SqliteRow> for Accounts { +impl<'r> FromRow<'r, SqliteRow> for ServerMembers { fn from_row(row: &'r SqliteRow) -> Result { let server_tmp: i64 = row.try_get("server")?; let server = GuildId::from(server_tmp as u64); @@ -127,9 +122,24 @@ impl<'r> FromRow<'r, SqliteRow> for Accounts { Ok(Self { server, id_wolves: row.try_get("id_wolves")?, - id_member: row.try_get("id_member")?, - email: row.try_get("email")?, expiry: row.try_get("expiry")?, + }) + } +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct Wolves { + pub id_wolves: String, + pub email: String, + pub discord: Option, + pub minecraft: Option, +} + +impl<'r> FromRow<'r, SqliteRow> for Wolves { + fn from_row(row: &'r SqliteRow) -> Result { + Ok(Self { + id_wolves: row.try_get("id_wolves")?, + email: row.try_get("email")?, discord: row.try_get("discord")?, minecraft: row.try_get("minecraft")?, }) @@ -180,38 +190,47 @@ pub async fn db_init(config: &Config) -> Result, Error> { let pool = SqlitePoolOptions::new() .max_connections(5) - .connect_with(SqliteConnectOptions::from_str(&format!("sqlite://{}", database))?.create_if_missing(true)) + .connect_with( + SqliteConnectOptions::from_str(&format!("sqlite://{}", database))? + .foreign_keys(true) + .create_if_missing(true), + ) .await?; sqlx::query( - "CREATE TABLE IF NOT EXISTS accounts ( - server integer not null, - id_wolves text DEFAULT '', - id_member text DEFAULT '', - email text not null, - expiry text not null, - discord text, - minecraft text, - PRIMARY KEY(server,id_wolves,id_member) - )", + "CREATE TABLE IF NOT EXISTS wolves ( + id_wolves text PRIMARY KEY, + email text not null, + discord text, + minecraft text + )", ) .execute(&pool) .await?; - sqlx::query("CREATE INDEX IF NOT EXISTS index_server ON accounts (server)").execute(&pool).await?; - sqlx::query("CREATE INDEX IF NOT EXISTS index_id_wolves ON accounts (id_wolves)") - .execute(&pool) - .await?; + sqlx::query("CREATE INDEX IF NOT EXISTS index_discord ON wolves (discord)").execute(&pool).await?; + + sqlx::query( + "CREATE TABLE IF NOT EXISTS server_members ( + server integer not null, + id_wolves text not null, + expiry text not null, + PRIMARY KEY(server,id_wolves), + FOREIGN KEY (id_wolves) REFERENCES wolves (id_wolves) + )", + ) + .execute(&pool) + .await?; sqlx::query( "CREATE TABLE IF NOT EXISTS servers ( - server integer key, - wolves_api text not null, - role_past integer, - role_current integer, - member_past integer DEFAULT 0, - member_current integer DEFAULT 0 - )", + server integer KEY, + wolves_api text not null, + role_past integer, + role_current integer, + member_past integer DEFAULT 0, + member_current integer DEFAULT 0 + )", ) .execute(&pool) .await?; @@ -222,10 +241,10 @@ pub async fn db_init(config: &Config) -> Result, Error> { pub async fn get_server_config(db: &Pool, server: &GuildId) -> Option { sqlx::query_as::<_, Servers>( r#" - SELECT * - FROM servers - WHERE server = ? - "#, + SELECT * + FROM servers + WHERE server = ? + "#, ) .bind(*server.as_u64() as i64) .fetch_one(db) @@ -233,19 +252,36 @@ pub async fn get_server_config(db: &Pool, server: &GuildId) -> Option, server: &GuildId, member: &guild::Member) -> Option { - sqlx::query_as::<_, Accounts>( +pub async fn get_server_member(db: &Pool, server: &GuildId, member: &guild::Member) -> Option { + let wolves_data = sqlx::query_as::<_, Wolves>( r#" - SELECT * - FROM accounts - WHERE server = ? AND discord = ? - "#, + SELECT * + FROM wolves + WHERE discord = ? + "#, ) .bind(*server.as_u64() as i64) .bind(&member.user.name) .fetch_one(db) - .await - .ok() + .await; + + if let Ok(user_wolves) = wolves_data { + // check if the suer is on the server + return sqlx::query_as::<_, ServerMembers>( + r#" + SELECT * + FROM server_members + WHERE server = ? AND id_wolves = ? + "#, + ) + .bind(*server.as_u64() as i64) + .bind(&user_wolves.id_wolves) + .fetch_one(db) + .await + .ok(); + } + + None } pub async fn get_server_config_bulk(db: &Pool) -> Vec { diff --git a/src/main.rs b/src/main.rs index 065d2d6..4cf4200 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,14 +4,16 @@ use serenity::{ async_trait, client::{Context, EventHandler}, model::{ + application::{ + command::Command, + interaction::{Interaction, InteractionResponseType}, + }, gateway::{GatewayIntents, Ready}, guild, - application::{interaction::{Interaction, InteractionResponseType}, command::Command}, }, Client, }; use std::sync::Arc; -use serenity::model::prelude::interaction; use skynet_discord_bot::{db_init, get_config, get_server_config, get_server_member, Config, DataBase}; use tokio::sync::RwLock; @@ -56,13 +58,11 @@ impl EventHandler for Handler { async fn ready(&self, ctx: Context, ready: Ready) { println!("[Main] {} is connected!", ready.user.name); - Command::set_global_application_commands(&ctx.http, |commands| { - commands - .create_application_command(|command| commands::link_email::register(command)) + commands.create_application_command(|command| commands::link_email::register(command)) }) - .await.ok(); - + .await + .ok(); } async fn interaction_create(&self, ctx: Context, interaction: Interaction) {