use serde::{Deserialize, Serialize}; use sqlx::{Pool, Sqlite}; use crate::common::database::Wolves; /** This file relates to anything that directly interacts with teh wolves API */ #[derive(Deserialize, Serialize, Debug)] struct WolvesResultUserMin { // committee: String, member_id: String, // first_name: String, // last_name: String, contact_email: String, // opt_in_email: String, // student_id: Option, // note: Option, // expiry: String, // requested: String, // approved: String, // sitename: String, // domain: String, } async fn add_users_wolves(db: &Pool, user: &WolvesResultUserMin) { // expiry match sqlx::query_as::<_, Wolves>( " INSERT INTO wolves (id_wolves, email) VALUES ($1, $2) ON CONFLICT(id_wolves) DO UPDATE SET email = $2 ", ) .bind(&user.member_id) .bind(&user.contact_email) .fetch_optional(db) .await { Ok(_) => {} Err(e) => { println!("Failure to insert into Wolves {:?}", user); println!("{:?}", e); } } } /** This is getting data for Clubs and Socs */ pub mod cns { use crate::common::set_roles::normal::update_server; use std::collections::BTreeMap; use serde::{Deserialize, Serialize}; use serenity::client::Context; use serenity::model::id::GuildId; use sqlx::{Pool, Sqlite}; use crate::Config; use crate::common::database::{get_server_config_bulk, DataBase, ServerMembers, ServerMembersWolves, Servers, Wolves}; use crate::common::wolves::{add_users_wolves, WolvesResultUserMin}; #[derive(Deserialize, Serialize, Debug)] struct WolvesResultUser { committee: String, member_id: String, first_name: String, last_name: String, contact_email: String, opt_in_email: String, student_id: Option, note: Option, expiry: String, requested: String, approved: String, sitename: String, domain: String, } impl From<&WolvesResultUser> for WolvesResultUserMin { fn from(value: &WolvesResultUser) -> Self { Self { member_id: value.member_id.to_owned(), contact_email: value.contact_email.to_owned(), } } } #[derive(Deserialize, Serialize, Debug)] struct WolvesResult { success: i8, result: Vec, } #[derive(Deserialize, Serialize, Debug)] struct WolvesResultLocal { pub id_wolves: String, pub email: String, pub expiry: String, } pub async fn get_wolves(ctx: &Context) { let db_lock = { let data_read = ctx.data.read().await; data_read.get::().expect("Expected Database in TypeMap.").clone() }; let db = db_lock.read().await; let config_lock = { let data_read = ctx.data.read().await; data_read.get::().expect("Expected Config in TypeMap.").clone() }; let config = config_lock.read().await; for server_config in get_server_config_bulk(&db).await { let Servers { server, wolves_api, .. } = &server_config; let existing_tmp = get_server_member(&db, server).await; let existing = existing_tmp.iter().map(|data| (data.id_wolves, data)).collect::>(); // list of users that need to be updated for this server let mut user_to_update = vec![]; for user in get_wolves_sub(&config, wolves_api).await { let id = user.member_id.parse::().unwrap_or_default(); match existing.get(&(id as i64)) { None => { // user does not exist already, add everything add_users_wolves(&db, &WolvesResultUserMin::from(&user)).await; add_users_server_members(&db, server, &user).await; } Some(old) => { // always update wolves table, in case data has changed add_users_wolves(&db, &WolvesResultUserMin::from(&user)).await; if old.expiry != user.expiry { add_users_server_members(&db, server, &user).await; if let Some(discord_id) = old.discord { user_to_update.push(discord_id); } } } } } if !user_to_update.is_empty() { update_server(ctx, &server_config, &[], &user_to_update).await; } } } async fn get_server_member(db: &Pool, server: &GuildId) -> Vec { sqlx::query_as::<_, ServerMembersWolves>( r#" SELECT * FROM server_members JOIN wolves USING (id_wolves) WHERE ( server = ? AND discord IS NOT NULL ) "#, ) .bind(*server.as_u64() as i64) .fetch_all(db) .await .unwrap_or_default() } async fn get_wolves_sub(config: &Config, wolves_api: &str) -> Vec { if config.wolves_url.is_empty() { return vec![]; } let url = format!("{}/get_members", &config.wolves_url); // get wolves data if let Ok(mut res) = surf::post(&url).header("X-AM-Identity", wolves_api).await { if let Ok(WolvesResult { success, result, }) = res.body_json().await { if success != 1 { return vec![]; } return result; } } vec![] } async fn add_users_server_members(db: &Pool, server: &GuildId, user: &WolvesResultUser) { match sqlx::query_as::<_, ServerMembers>( " INSERT OR REPLACE INTO server_members (server, id_wolves, expiry) VALUES (?1, ?2, ?3) ", ) .bind(*server.as_u64() as i64) .bind(&user.member_id) .bind(&user.expiry) .fetch_optional(db) .await { Ok(_) => {} Err(e) => { println!("Failure to insert into ServerMembers {} {:?}", server.as_u64(), user); println!("{:?}", e); } } } } /** Get and store the data on C&S committees */ pub mod committees { use serde::{Deserialize, Serialize}; use serenity::client::Context; use sqlx::{ Pool, Sqlite}; use crate::common::database::{DataBase}; use crate::Config; // This is what Wolves returns to us #[derive(Deserialize, Serialize, Debug)] struct WolvesResult { success: i8, result: Vec, } // this is teh actual data we care about #[derive(Deserialize, Serialize, Debug)] struct WolvesResultCNS { id: String, name: String, // Link to their page such as https://ulwolves.ie/society/computer link: String, // array of Committee members member_id's committee: Vec } // Database entry for it #[derive(Debug, Clone, sqlx::FromRow)] pub struct Committees { pub id: i64, pub name: String, pub link: String, #[sqlx(json)] pub committee: Vec, } impl From for Committees { fn from(value: WolvesResultCNS) -> Self { Self{ id: value.id.parse().unwrap_or(0), name: value.name, link: value.link, committee: value.committee.iter().map(|x| x.parse::().unwrap_or(0)).collect(), } } } pub async fn get_cns(ctx: &Context){ let db_lock = { let data_read = ctx.data.read().await; data_read.get::().expect("Expected Database in TypeMap.").clone() }; let db = db_lock.read().await; let config_lock = { let data_read = ctx.data.read().await; data_read.get::().expect("Expected Config in TypeMap.").clone() }; let config = config_lock.read().await; // TODO: proper api key management let api_key = ""; // request data from wolves for committee in get_committees(&config, api_key).await { let tmp = Committees::from(committee); add_committee(&db, &tmp).await; } } async fn get_committees(config: &Config, wolves_api: &str) -> Vec { if config.wolves_url.is_empty() { return vec![]; } // TODO: Change teh stored env value to teh base domain let url = format!("{}/get_cns", &config.wolves_url); // get wolves data if let Ok(mut res) = surf::post(&url).header("X-AM-Identity", wolves_api).await { if let Ok(WolvesResult { success, result, }) = res.body_json().await { if success != 1 { return vec![]; } return result; } } vec![] } async fn add_committee(db: &Pool, committee: &Committees) { match sqlx::query_as::<_, Committees>( " INSERT INTO committees (id, name, link, committee) VALUES ($1, $2, $3, $4) ON CONFLICT(id) DO UPDATE SET committee = $4 ", ) .bind(committee.id) .bind(&committee.name) .bind(&committee.link) .bind(serde_json::to_string(&committee.committee).unwrap_or_default()) .fetch_optional(db) .await { Ok(_) => {} Err(e) => { println!("Failure to insert into Committees {:?}", committee); println!("{:?}", e); } } } } /** get the data for an individual user */ pub mod individual { use serde::{Deserialize, Serialize}; use serenity::client::Context; use crate::common::database::DataBase; use crate::common::wolves::{add_users_wolves, WolvesResultUserMin}; use crate::Config; #[derive(Deserialize, Serialize, Debug)] struct WolvesResultUser { // committee: String, member_id: String, // first_name: String, // last_name: String, contact_email: String, // opt_in_email: String, // student_id: Option, // note: Option, // expiry: String, // requested: String, // approved: String, // sitename: String, // domain: String, } impl From<&WolvesResultUser> for WolvesResultUserMin { fn from(value: &WolvesResultUser) -> Self { Self { member_id: value.member_id.to_owned(), contact_email: value.contact_email.to_owned(), } } } #[derive(Deserialize, Serialize, Debug)] struct WolvesResult { success: i8, result: WolvesResultUser, } pub async fn get_user(ctx: &Context, email: &str) -> bool{ let db_lock = { let data_read = ctx.data.read().await; data_read.get::().expect("Expected Database in TypeMap.").clone() }; let db = db_lock.read().await; let config_lock = { let data_read = ctx.data.read().await; data_read.get::().expect("Expected Config in TypeMap.").clone() }; let config = config_lock.read().await; // TODO: proper api key management let api_key = ""; // request data from wolves match get_user_sub(&config, api_key, email).await { None => { false } // if exists save it and return true Some(user) => { // add to db add_users_wolves(&db, &WolvesResultUserMin::from(&user)).await; true } } } async fn get_user_sub(config: &Config, wolves_api: &str, email: &str) -> Option { if config.wolves_url.is_empty() { return None; } // TODO: Change teh stored env value to teh base domain let url = format!("{}/get_member", &config.wolves_url); // get wolves data if let Ok(mut res) = surf::post(&url).header("X-AM-Identity", wolves_api).await { if let Ok(WolvesResult { success, result, }) = res.body_json().await { if success != 1 { return None; } return Some(result); } } None } }