From f11fdb4dde603b0d39aacf2f6015ce0bc96557da Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Mon, 25 Sep 2023 18:50:33 +0100 Subject: [PATCH 1/3] feat: conversion script --- Cargo.toml | 3 + src/bin/update_id.rs | 144 +++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 38 ++++++++++++ 3 files changed, 185 insertions(+) create mode 100644 src/bin/update_id.rs diff --git a/Cargo.toml b/Cargo.toml index 0732349..54043e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,9 @@ name = "update_data" [[bin]] name = "update_users" +[[bin]] +name = "update_id" + [dependencies] serenity = { version = "0.11.6", default-features = false, features = ["client", "gateway", "rustls_backend", "model", "cache"] } diff --git a/src/bin/update_id.rs b/src/bin/update_id.rs new file mode 100644 index 0000000..4923801 --- /dev/null +++ b/src/bin/update_id.rs @@ -0,0 +1,144 @@ +use serenity::{ + async_trait, + client::{Context, EventHandler}, + model::{ + gateway::{GatewayIntents, Ready}, + }, + Client, +}; +use skynet_discord_bot::{db_init, get_config, Config, DataBase, ServerMembersWolves, Wolves2}; +use sqlx::{Pool, Sqlite}; +use std::{process, sync::Arc}; +use tokio::sync::RwLock; + +#[tokio::main] +async fn main() { + let config = get_config(); + let db = match db_init(&config).await { + Ok(x) => x, + Err(_) => return, + }; + + // Intents are a bitflag, bitwise operations can be used to dictate which intents to use + let intents = GatewayIntents::GUILDS | GatewayIntents::GUILD_MESSAGES | GatewayIntents::MESSAGE_CONTENT | GatewayIntents::GUILD_MEMBERS; + // Build our client. + let mut client = Client::builder(&config.discord_token, intents) + .event_handler(Handler {}) + .await + .expect("Error creating client"); + + { + let mut data = client.data.write().await; + + data.insert::(Arc::new(RwLock::new(config))); + data.insert::(Arc::new(RwLock::new(db))); + } + + if let Err(why) = client.start().await { + println!("Client error: {:?}", why); + } +} + +struct Handler; +#[async_trait] +impl EventHandler for Handler { + async fn ready(&self, ctx: Context, ready: Ready) { + let ctx = Arc::new(ctx); + println!("{} is connected!", ready.user.name); + + bulk_check(Arc::clone(&ctx)).await; + + // finish up + process::exit(0); + } +} + +async fn bulk_check(ctx: Arc) { + let db_lock = { + let data_read = ctx.data.read().await; + data_read.get::().expect("Expected Config in TypeMap.").clone() + }; + + let db = db_lock.read().await; + + // get sk + let mut wolves2 = vec![]; + + // get wolves users + for user in get_server_member_bulk(&db).await{ + let discord = match user.discord{ + None => { + wolves2.push(Wolves2{ + id_wolves: user.id_wolves, + email: user.email, + discord: None, + minecraft: user.minecraft, + }); + + continue; + } + Some(x) => {x} + }; + + if let Ok(x) = user.server.search_members(&ctx.http, &discord, None).await { + for user_tmp in x { + if &user_tmp.user.name == &discord { + wolves2.push(Wolves2{ + id_wolves: user.id_wolves.to_owned(), + email: user.email.to_owned(), + discord: Some(user_tmp.user.id), + minecraft: user.minecraft.to_owned(), + }); + + break; + } + } + } + } + + for user in wolves2 { + + set_member_info(&db, &user).await; + } +} + +async fn get_server_member_bulk(db: &Pool) -> Vec { + sqlx::query_as::<_, ServerMembersWolves>( + r#" + SELECT * + FROM server_members + JOIN wolves USING (id_wolves) + "#, + ) + .fetch_all(db) + .await + .unwrap_or_default() +} + +async fn set_member_info(db: &Pool, user: &Wolves2){ + + let discord = user.discord.map(|x| *x.as_u64() as i64); + + + // save + match sqlx::query_as::<_, Wolves2>( + " + INSERT INTO wolves2 (id_wolves, email, discord) + VALUES ($1, $2, $3) + ON CONFLICT(id_wolves) DO UPDATE SET email = $2, discord = $3 + ", + ) + .bind(&user.id_wolves) + .bind(&user.email) + .bind(discord) + .fetch_optional(db) + .await + { + Ok(_) => {} + Err(e) => { + println!("Failure to insert into {:?}", &user); + println!("{:?}", e); + } + } + +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index cd5ee35..f148b4d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,6 +15,7 @@ use sqlx::{ Error, FromRow, Pool, Row, Sqlite, }; use std::{env, str::FromStr, sync::Arc}; +use serenity::model::id::UserId; use tokio::sync::RwLock; pub struct Config { @@ -152,6 +153,32 @@ pub struct Wolves { pub minecraft: Option, } +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct Wolves2 { + pub id_wolves: String, + pub email: String, + pub discord: Option, + pub minecraft: Option, +} +impl<'r> FromRow<'r, SqliteRow> for Wolves2 { + fn from_row(row: &'r SqliteRow) -> Result { + let discord = match row.try_get("discord") { + Ok(x) => { + let tmp: i64 = x; + Some(UserId::from(tmp as u64)) + } + _ => None, + }; + + Ok(Self { + id_wolves: row.try_get("id_wolves")?, + email: row.try_get("email")?, + discord, + minecraft: row.try_get("minecraft")?, + }) + } +} + #[derive(Debug, Clone, Deserialize, Serialize, sqlx::FromRow)] pub struct WolvesVerify { pub email: String, @@ -222,6 +249,17 @@ pub async fn db_init(config: &Config) -> Result, Error> { .execute(&pool) .await?; + sqlx::query( + "CREATE TABLE IF NOT EXISTS wolves2 ( + id_wolves text PRIMARY KEY, + email text not null, + discord integer, + minecraft text + )", + ) + .execute(&pool) + .await?; + sqlx::query("CREATE INDEX IF NOT EXISTS index_discord ON wolves (discord)").execute(&pool).await?; sqlx::query( From 8760f4440a1d31f1f264880cb544a9cab3a3731c Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Tue, 26 Sep 2023 00:57:03 +0100 Subject: [PATCH 2/3] feat: completed migration Closes #4 --- Cargo.toml | 4 -- src/bin/update_data.rs | 32 +-------- src/bin/update_id.rs | 144 ------------------------------------- src/bin/update_users.rs | 2 +- src/commands/add_server.rs | 6 +- src/commands/link_email.rs | 13 ++-- src/lib.rs | 61 ++++++++-------- 7 files changed, 43 insertions(+), 219 deletions(-) delete mode 100644 src/bin/update_id.rs diff --git a/Cargo.toml b/Cargo.toml index 54043e3..3906510 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,10 +11,6 @@ name = "update_data" [[bin]] name = "update_users" -[[bin]] -name = "update_id" - - [dependencies] serenity = { version = "0.11.6", default-features = false, features = ["client", "gateway", "rustls_backend", "model", "cache"] } tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] } diff --git a/src/bin/update_data.rs b/src/bin/update_data.rs index 1fc25de..0db63e6 100644 --- a/src/bin/update_data.rs +++ b/src/bin/update_data.rs @@ -16,8 +16,7 @@ async fn main() { get_wolves_csv(&db, &config).await; // handle wolves api here get_wolves(&db).await; - // get from skynet for the compsoc server only - get_skynet(&db, &config).await; + } async fn get_wolves_csv(db: &Pool, config: &Config) { @@ -133,35 +132,6 @@ 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, &user).await; - } - } -} - -async fn add_users_skynet(db: &Pool, user: &SkynetResult) { - match sqlx::query_as::<_, Wolves>( - " - UPDATE wolves - SET discord = ? - WHERE id_wolves = ? - ", - ) - .bind(&user.discord) - .bind(&user.id_member) - .fetch_optional(db) - .await - { - Ok(_) => {} - Err(e) => { - println!("Failure to insert skynet user into database {:?}", user); - println!("{:?}", e); - } - } -} #[derive(Debug, Deserialize)] struct WolvesResult { diff --git a/src/bin/update_id.rs b/src/bin/update_id.rs deleted file mode 100644 index 4923801..0000000 --- a/src/bin/update_id.rs +++ /dev/null @@ -1,144 +0,0 @@ -use serenity::{ - async_trait, - client::{Context, EventHandler}, - model::{ - gateway::{GatewayIntents, Ready}, - }, - Client, -}; -use skynet_discord_bot::{db_init, get_config, Config, DataBase, ServerMembersWolves, Wolves2}; -use sqlx::{Pool, Sqlite}; -use std::{process, sync::Arc}; -use tokio::sync::RwLock; - -#[tokio::main] -async fn main() { - let config = get_config(); - let db = match db_init(&config).await { - Ok(x) => x, - Err(_) => return, - }; - - // Intents are a bitflag, bitwise operations can be used to dictate which intents to use - let intents = GatewayIntents::GUILDS | GatewayIntents::GUILD_MESSAGES | GatewayIntents::MESSAGE_CONTENT | GatewayIntents::GUILD_MEMBERS; - // Build our client. - let mut client = Client::builder(&config.discord_token, intents) - .event_handler(Handler {}) - .await - .expect("Error creating client"); - - { - let mut data = client.data.write().await; - - data.insert::(Arc::new(RwLock::new(config))); - data.insert::(Arc::new(RwLock::new(db))); - } - - if let Err(why) = client.start().await { - println!("Client error: {:?}", why); - } -} - -struct Handler; -#[async_trait] -impl EventHandler for Handler { - async fn ready(&self, ctx: Context, ready: Ready) { - let ctx = Arc::new(ctx); - println!("{} is connected!", ready.user.name); - - bulk_check(Arc::clone(&ctx)).await; - - // finish up - process::exit(0); - } -} - -async fn bulk_check(ctx: Arc) { - let db_lock = { - let data_read = ctx.data.read().await; - data_read.get::().expect("Expected Config in TypeMap.").clone() - }; - - let db = db_lock.read().await; - - // get sk - let mut wolves2 = vec![]; - - // get wolves users - for user in get_server_member_bulk(&db).await{ - let discord = match user.discord{ - None => { - wolves2.push(Wolves2{ - id_wolves: user.id_wolves, - email: user.email, - discord: None, - minecraft: user.minecraft, - }); - - continue; - } - Some(x) => {x} - }; - - if let Ok(x) = user.server.search_members(&ctx.http, &discord, None).await { - for user_tmp in x { - if &user_tmp.user.name == &discord { - wolves2.push(Wolves2{ - id_wolves: user.id_wolves.to_owned(), - email: user.email.to_owned(), - discord: Some(user_tmp.user.id), - minecraft: user.minecraft.to_owned(), - }); - - break; - } - } - } - } - - for user in wolves2 { - - set_member_info(&db, &user).await; - } -} - -async fn get_server_member_bulk(db: &Pool) -> Vec { - sqlx::query_as::<_, ServerMembersWolves>( - r#" - SELECT * - FROM server_members - JOIN wolves USING (id_wolves) - "#, - ) - .fetch_all(db) - .await - .unwrap_or_default() -} - -async fn set_member_info(db: &Pool, user: &Wolves2){ - - let discord = user.discord.map(|x| *x.as_u64() as i64); - - - // save - match sqlx::query_as::<_, Wolves2>( - " - INSERT INTO wolves2 (id_wolves, email, discord) - VALUES ($1, $2, $3) - ON CONFLICT(id_wolves) DO UPDATE SET email = $2, discord = $3 - ", - ) - .bind(&user.id_wolves) - .bind(&user.email) - .bind(discord) - .fetch_optional(db) - .await - { - Ok(_) => {} - Err(e) => { - println!("Failure to insert into {:?}", &user); - println!("{:?}", e); - } - } - -} \ No newline at end of file diff --git a/src/bin/update_users.rs b/src/bin/update_users.rs index 28557c3..6b66cfb 100644 --- a/src/bin/update_users.rs +++ b/src/bin/update_users.rs @@ -82,7 +82,7 @@ async fn bulk_check(ctx: Arc) { if let Ok(x) = server.members(&ctx, None, None).await { for mut member in x { - if members.contains(&member.user.name) { + if members.contains(&member.user.id) { let mut roles = vec![]; if let Some(role) = &role_past { diff --git a/src/commands/add_server.rs b/src/commands/add_server.rs index 0f112a1..5f25646 100644 --- a/src/commands/add_server.rs +++ b/src/commands/add_server.rs @@ -6,7 +6,7 @@ use serenity::{ prelude::{command::CommandOptionType, interaction::application_command::CommandDataOptionValue}, }, }; -use skynet_discord_bot::{DataBase, Servers, Wolves}; +use skynet_discord_bot::{DataBase, Servers}; use sqlx::{Error, Pool, Sqlite}; pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> String { @@ -130,11 +130,11 @@ pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicatio }) } -async fn add_server(db: &Pool, server: &Servers) -> Result, Error> { +async fn add_server(db: &Pool, server: &Servers) -> Result, Error> { let role_past = server.role_past.map(|x| *x.as_u64() as i64); let role_current = server.role_current.map(|x| *x.as_u64() as i64); - sqlx::query_as::<_, Wolves>( + sqlx::query_as::<_, Servers>( " INSERT OR REPLACE INTO servers (server, wolves_api, role_past, role_current) VALUES (?1, ?2, ?3, ?4) diff --git a/src/commands/link_email.rs b/src/commands/link_email.rs index 2d96ce3..7148af4 100644 --- a/src/commands/link_email.rs +++ b/src/commands/link_email.rs @@ -16,6 +16,7 @@ use skynet_discord_bot::{get_now_iso, random_string, Config, DataBase, Wolves, W use sqlx::{Pool, Sqlite}; pub(crate) mod link { + use serenity::model::id::UserId; use super::*; pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> String { @@ -31,7 +32,7 @@ pub(crate) mod link { }; let config = config_lock.read().await; - if get_server_member_discord(&db, &command.user.name).await.is_some() { + if get_server_member_discord(&db, &command.user.id).await.is_some() { return "Already linked".to_string(); } @@ -71,7 +72,7 @@ pub(crate) mod link { // generate a auth key let auth = random_string(20); match send_mail(&config, &details, &auth, &command.user.name) { - Ok(_) => match save_to_db(&db, &details, &auth, &command.user.name).await { + Ok(_) => match save_to_db(&db, &details, &auth, &command.user.id).await { Ok(_) => {} Err(e) => { return format!("Unable to save to db {} {e:?}", &details.email); @@ -92,7 +93,7 @@ pub(crate) mod link { .create_option(|option| option.name("email").description("UL Wolves Email").kind(CommandOptionType::String).required(true)) } - async fn get_server_member_discord(db: &Pool, user: &str) -> Option { + async fn get_server_member_discord(db: &Pool, user: &UserId) -> Option { sqlx::query_as::<_, Wolves>( r#" SELECT * @@ -100,7 +101,7 @@ pub(crate) mod link { WHERE discord = ? "#, ) - .bind(user) + .bind(*user.as_u64() as i64) .fetch_one(db) .await .ok() @@ -218,7 +219,7 @@ pub(crate) mod link { .ok() } - async fn save_to_db(db: &Pool, record: &Wolves, auth: &str, user: &str) -> Result, sqlx::Error> { + async fn save_to_db(db: &Pool, record: &Wolves, auth: &str, user: &UserId) -> Result, sqlx::Error> { sqlx::query_as::<_, WolvesVerify>( " INSERT INTO wolves_verify (email, discord, auth_code, date_expiry) @@ -226,7 +227,7 @@ pub(crate) mod link { ", ) .bind(record.email.to_owned()) - .bind(user) + .bind(*user.as_u64() as i64) .bind(auth.to_owned()) .bind(get_now_iso(false)) .fetch_optional(db) diff --git a/src/lib.rs b/src/lib.rs index f148b4d..2dea62d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -126,41 +126,40 @@ pub struct ServerMembersWolves { pub id_wolves: String, pub expiry: String, pub email: String, - pub discord: Option, + pub discord: Option, pub minecraft: Option, } impl<'r> FromRow<'r, SqliteRow> for ServerMembersWolves { fn from_row(row: &'r SqliteRow) -> Result { let server_tmp: i64 = row.try_get("server")?; let server = GuildId::from(server_tmp as u64); - + let discord = match row.try_get("discord") { + Ok(x) => { + let tmp: i64 = x; + Some(UserId::from(tmp as u64)) + } + _ => None, + }; + Ok(Self { server, id_wolves: row.try_get("id_wolves")?, expiry: row.try_get("expiry")?, email: row.try_get("email")?, - discord: row.try_get("discord")?, + discord, minecraft: row.try_get("minecraft")?, }) } } -#[derive(Debug, Clone, Deserialize, Serialize, sqlx::FromRow)] -pub struct Wolves { - pub id_wolves: String, - pub email: String, - pub discord: Option, - pub minecraft: Option, -} - #[derive(Debug, Clone, Deserialize, Serialize)] -pub struct Wolves2 { +pub struct Wolves { pub id_wolves: String, pub email: String, pub discord: Option, pub minecraft: Option, } -impl<'r> FromRow<'r, SqliteRow> for Wolves2 { +impl<'r> FromRow<'r, SqliteRow> for Wolves { fn from_row(row: &'r SqliteRow) -> Result { let discord = match row.try_get("discord") { Ok(x) => { @@ -179,13 +178,26 @@ impl<'r> FromRow<'r, SqliteRow> for Wolves2 { } } -#[derive(Debug, Clone, Deserialize, Serialize, sqlx::FromRow)] +#[derive(Debug, Clone, Deserialize, Serialize)] pub struct WolvesVerify { pub email: String, - pub discord: String, + pub discord: UserId, pub auth_code: String, pub date_expiry: String, } +impl<'r> FromRow<'r, SqliteRow> for WolvesVerify { + fn from_row(row: &'r SqliteRow) -> Result { + let user_tmp: i64 = row.try_get("discord")?; + let discord = UserId::from(user_tmp as u64); + + Ok(Self { + email: row.try_get("email")?, + discord, + auth_code: row.try_get("auth_code")?, + date_expiry: row.try_get("date_expiry")?, + }) + } +} #[derive(Debug, Clone, Deserialize, Serialize)] pub struct Servers { @@ -240,17 +252,6 @@ pub async fn db_init(config: &Config) -> Result, Error> { sqlx::query( "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 TABLE IF NOT EXISTS wolves2 ( id_wolves text PRIMARY KEY, email text not null, discord integer, @@ -264,10 +265,10 @@ pub async fn db_init(config: &Config) -> Result, Error> { sqlx::query( "CREATE TABLE IF NOT EXISTS wolves_verify ( - discord text PRIMARY KEY, - email text not null, - auth_code text not null, - date_expiry text not null + discord integer PRIMARY KEY, + email text not null, + auth_code text not null, + date_expiry text not null )", ) .execute(&pool) From abb00ff8c7f504cfbee85371e97cfa448d2bbcef Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Tue, 26 Sep 2023 01:04:07 +0100 Subject: [PATCH 3/3] fmt: danm formatting --- src/bin/update_data.rs | 1 - src/commands/link_email.rs | 2 +- src/lib.rs | 10 +++++----- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/bin/update_data.rs b/src/bin/update_data.rs index 0db63e6..bee6e97 100644 --- a/src/bin/update_data.rs +++ b/src/bin/update_data.rs @@ -16,7 +16,6 @@ async fn main() { get_wolves_csv(&db, &config).await; // handle wolves api here get_wolves(&db).await; - } async fn get_wolves_csv(db: &Pool, config: &Config) { diff --git a/src/commands/link_email.rs b/src/commands/link_email.rs index 7148af4..4ca115e 100644 --- a/src/commands/link_email.rs +++ b/src/commands/link_email.rs @@ -16,8 +16,8 @@ use skynet_discord_bot::{get_now_iso, random_string, Config, DataBase, Wolves, W use sqlx::{Pool, Sqlite}; pub(crate) mod link { - use serenity::model::id::UserId; use super::*; + use serenity::model::id::UserId; pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> String { let db_lock = { diff --git a/src/lib.rs b/src/lib.rs index 2dea62d..ebb7c2a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,12 +10,12 @@ use serenity::{ use chrono::{Datelike, SecondsFormat, Utc}; use rand::{distributions::Alphanumeric, thread_rng, Rng}; +use serenity::model::id::UserId; use sqlx::{ sqlite::{SqliteConnectOptions, SqlitePoolOptions, SqliteRow}, Error, FromRow, Pool, Row, Sqlite, }; use std::{env, str::FromStr, sync::Arc}; -use serenity::model::id::UserId; use tokio::sync::RwLock; pub struct Config { @@ -140,7 +140,7 @@ impl<'r> FromRow<'r, SqliteRow> for ServerMembersWolves { } _ => None, }; - + Ok(Self { server, id_wolves: row.try_get("id_wolves")?, @@ -168,7 +168,7 @@ impl<'r> FromRow<'r, SqliteRow> for Wolves { } _ => None, }; - + Ok(Self { id_wolves: row.try_get("id_wolves")?, email: row.try_get("email")?, @@ -258,8 +258,8 @@ pub async fn db_init(config: &Config) -> Result, Error> { minecraft text )", ) - .execute(&pool) - .await?; + .execute(&pool) + .await?; sqlx::query("CREATE INDEX IF NOT EXISTS index_discord ON wolves (discord)").execute(&pool).await?;