From 555e38ee26c40b5c2728032bf8daed6484dbea18 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Thu, 27 Feb 2025 01:19:40 +0000 Subject: [PATCH] feat: now store the roles and channel ids in teh database --- db/migrations/10_member_committee-roles.sql | 10 + src/common/database.rs | 24 +- src/common/set_roles.rs | 258 ++++++++++++++------ 3 files changed, 209 insertions(+), 83 deletions(-) create mode 100644 db/migrations/10_member_committee-roles.sql diff --git a/db/migrations/10_member_committee-roles.sql b/db/migrations/10_member_committee-roles.sql new file mode 100644 index 0000000..03264cd --- /dev/null +++ b/db/migrations/10_member_committee-roles.sql @@ -0,0 +1,10 @@ + +CREATE TABLE IF NOT EXISTS committee_roles ( + id_wolves integer PRIMARY KEY, + id_role integer DEFAULT 1, + id_channel integer DEFAULT 1, + -- not strictly required but for readability and debugging + name_role text NOT NULL DEFAULT '', + name_channel text NOT NULL DEFAULT '', + count integer DEFAULT 0 +); diff --git a/src/common/database.rs b/src/common/database.rs index e4856fa..0663799 100644 --- a/src/common/database.rs +++ b/src/common/database.rs @@ -190,14 +190,28 @@ impl<'r> FromRow<'r, SqliteRow> for RoleAdder { } } -fn get_role_from_row(row: &SqliteRow, col: &str) -> RoleId { - match row.try_get(col) { +pub(crate) fn get_role_from_row(row: &SqliteRow, col: &str) -> RoleId { + let id = match row.try_get(col) { Ok(x) => { let tmp: i64 = x; - RoleId::new(tmp as u64) + tmp as u64 } - _ => RoleId::from(0u64), - } + _ => 0, + }; + + RoleId::from(id) +} + +pub(crate) fn get_channel_from_row(row: &SqliteRow, col: &str) -> ChannelId { + let id = match row.try_get(col) { + Ok(x) => { + let tmp: i64 = x; + tmp as u64 + } + _ => 0, + }; + + ChannelId::from(id) } pub async fn db_init(config: &Config) -> Result, Error> { diff --git a/src/common/set_roles.rs b/src/common/set_roles.rs index 5710fb3..da8d9d1 100644 --- a/src/common/set_roles.rs +++ b/src/common/set_roles.rs @@ -131,9 +131,10 @@ pub mod normal { // for updating committee members pub mod committee { - use crate::common::database::{DataBase, Wolves}; + use crate::common::database::{get_channel_from_row, get_role_from_row, DataBase, Wolves}; use crate::common::wolves::committees::Committees; use crate::Config; + use serde::{Deserialize, Serialize}; use serenity::all::EditRole; use serenity::builder::CreateChannel; use serenity::client::Context; @@ -141,7 +142,8 @@ pub mod committee { use serenity::model::guild::Member; use serenity::model::id::ChannelId; use serenity::model::prelude::RoleId; - use sqlx::{Pool, Sqlite}; + use sqlx::sqlite::SqliteRow; + use sqlx::{Error, FromRow, Pool, Row, Sqlite}; use std::collections::HashMap; use std::sync::Arc; @@ -174,8 +176,7 @@ pub mod committee { let server = config.committee_server; let committee_member = RoleId::new(1226602779968274573); let committees = get_committees(db).await; - let categories = vec![ - // C&S Chats 1 + let categories = [ ChannelId::new(1226606560973815839), // C&S Chats 2 ChannelId::new(1341457244973305927), @@ -184,35 +185,30 @@ pub mod committee { ]; // information about the server - let roles = server.roles(&ctx).await.unwrap_or_default(); - let channels = server.channels(&ctx).await.unwrap_or_default(); - - // make a hashmap of the nameof roles to quickly get them out again - let mut roles_name = HashMap::new(); - for role in roles.values() { - roles_name.insert(role.name.to_owned(), role.to_owned()); + let mut roles_db = HashMap::new(); + for role in db_roles_get(db).await { + roles_db.insert( + role.id_wolves, + CommitteeRoles { + id_wolves: role.id_wolves, + id_role: role.id_role, + id_channel: role.id_channel, + name_role: role.name_role, + name_channel: role.name_channel, + // always start at 0 + count: 0, + }, + ); } - let mut channels_name = HashMap::new(); - for channel in channels.values() { - // we only care about teh channels in teh category - if let Some(x) = channel.parent_id { - for category in &categories { - if x.eq(category) { - channels_name.insert(channel.name.to_owned(), channel.to_owned()); - } - } - } - } + let mut channels = server.channels(&ctx).await.unwrap_or_default(); // a map of users and the roles they are goign to be getting let mut users_roles = HashMap::new(); - // a list of all the roles that can be removed from folks who should have them - let mut committee_roles = vec![committee_member]; - + let mut re_order = false; + // we need to create roles and channels if tehy dont already exist let mut category_index = 0; - let mut i = 0; loop { if i >= committees.len() { @@ -220,24 +216,17 @@ pub mod committee { } let committee = &committees[i]; - // get the role for this committee/club/soc - let role = match roles_name.get(&committee.name_full) { - Some(x) => Some(x.to_owned()), - None => { - // create teh role if it does not exist - match server - .create_role(&ctx, EditRole::new().name(&committee.name_full).hoist(false).mentionable(true)) - .await - { - Ok(x) => Some(x), - Err(_) => None, - } - } - }; - - // create teh channel if it does nto exist - if !channels_name.contains_key(&committee.name_profile) { - match server + // if a club/soc ever changes their name + if let Some(x) = roles_db.get_mut(&committee.id) { + x.name_role = committee.name_full.to_owned(); + x.name_channel = committee.name_profile.to_owned(); + } + + // handle new clubs/socs + if let std::collections::hash_map::Entry::Vacant(e) = roles_db.entry(committee.id) { + // create channel + // channel is first as the categories can only contain 50 channels + let channel = match server .create_channel( &ctx, CreateChannel::new(&committee.name_profile) @@ -247,40 +236,72 @@ pub mod committee { .await { Ok(x) => { - // update teh channels name list - channels_name.insert(x.name.to_owned(), x.to_owned()); - println!("Created channel: {}", &committee.name_profile); + + x.id } Err(x) => { let tmp = x.to_string(); - + dbg!("Unable to create channel: ", &tmp, &tmp.contains("Maximum number of channels in category reached (50)")); if x.to_string().contains("Maximum number of channels in category reached (50)") { category_index += 1; continue; } - dbg!("Unable to create channel: ", &tmp, &tmp.contains("Maximum number of channels in category reached (50)")); + ChannelId::new(1) } - } + }; + + // create role + let role = match server + .create_role(&ctx, EditRole::new().name(&committee.name_full).hoist(false).mentionable(true)) + .await + { + Ok(x) => x.id, + Err(_) => RoleId::new(1), + }; + + let tmp = CommitteeRoles { + id_wolves: committee.id, + id_role: role, + id_channel: channel, + name_role: committee.name_full.to_owned(), + name_channel: committee.name_profile.to_owned(), + count: 0, + }; + + // save it to the db in case of crash or error + db_role_set(db, &tmp).await; + + // insert it into teh local cache + e.insert(tmp); + + re_order = true; + } + + i += 1; + } + + for committee in &committees { + let r = if let Some(x) = roles_db.get(&committee.id) { + x.id_role + } else { + continue; }; - // so if the role exists - if let Some(r) = role { - committee_roles.push(r.id); + for id_wolves in &committee.committee { + // ID in this is the wolves ID, so we need to get a matching discord ID (if one exists) + if let Some(x) = get_server_member_discord(db, id_wolves).await { + if let Some(member_tmp) = x.discord { + let values = users_roles.entry(member_tmp).or_insert(vec![]); + values.push(r); - for id_wolves in &committee.committee { - // ID in this is the wolves ID, so we need to get a matching discord ID (if one exists) - if let Some(x) = get_server_member_discord(db, id_wolves).await { - if let Some(member_tmp) = x.discord { - let values = users_roles.entry(member_tmp).or_insert(vec![]); - values.push(r.id); + if let Some(x) = roles_db.get_mut(&committee.id) { + x.count += 1; } } } } - - i += 1; } // now we have a map of all users that should get roles time to go through all the folks on teh server @@ -315,6 +336,10 @@ pub mod committee { if !roles_required.is_empty() { // if there are committee roles then give the general purporse role roles_add.push(committee_member); + + if let Some(x) = roles_db.get_mut(&0) { + x.count += 1; + } } for role in &roles_required { @@ -335,33 +360,110 @@ pub mod committee { } } - // finally re-order teh channels to make them visually apealing - let mut channel_names = channels_name.clone().into_keys().collect::>(); - channel_names.sort(); + let mut channel_names = vec![]; + for role in roles_db.values() { + // save these to db + db_role_set(db, role).await; - // get a list of all teh new positions - let mut new_positions = vec![]; - for (i, name) in channel_names.iter().enumerate() { - if let Some(channel) = channels_name.get_mut(name) { - let position_new = i as u64; - if position_new != channel.position as u64 { - new_positions.push((channel.id.to_owned(), position_new)); - } + // pull out teh channel names + if re_order { + channel_names.push((role.name_channel.to_owned(), role.id_channel)); } } - if !new_positions.is_empty() { - match server.reorder_channels(&ctx, new_positions).await { - Ok(_) => { - println!("Successfully re-orderd the committee category"); + if re_order { + channel_names.sort_by_key(|(name, _)| name.to_owned()); + + // get a list of all teh new positions + let mut new_positions = vec![]; + for (i, (_, id)) in channel_names.iter().enumerate() { + if let Some(channel) = channels.get_mut(id) { + let position_new = i as u64; + if position_new != channel.position as u64 { + new_positions.push((channel.id.to_owned(), position_new)); + } } - Err(e) => { - dbg!("Failed to re-order ", e); + } + + if !new_positions.is_empty() { + match server.reorder_channels(&ctx, new_positions).await { + Ok(_) => { + println!("Successfully re-orderd the committee category"); + } + Err(e) => { + dbg!("Failed to re-order ", e); + } } } } } + #[derive(Debug, Clone, Deserialize, Serialize)] + struct CommitteeRoles { + id_wolves: i64, + id_role: RoleId, + id_channel: ChannelId, + name_role: String, + name_channel: String, + count: i64, + } + + impl<'r> FromRow<'r, SqliteRow> for CommitteeRoles { + fn from_row(row: &'r SqliteRow) -> Result { + Ok(Self { + id_wolves: row.try_get("id_wolves")?, + id_role: get_role_from_row(row, "id_role"), + id_channel: get_channel_from_row(row, "id_channel"), + name_role: row.try_get("name_role")?, + name_channel: row.try_get("name_channel")?, + count: row.try_get("count")?, + }) + } + } + + async fn db_role_set(db: &Pool, role: &CommitteeRoles) { + // expiry + match sqlx::query_as::<_, CommitteeRoles>( + " + INSERT INTO committee_roles (id_wolves, id_role, id_channel, name_role, name_channel, count) + VALUES ($1, $2, $3, $4, $5, $6) + ON CONFLICT(id_wolves) DO UPDATE SET name_role = $4, name_channel = $4, count = $6 + ", + ) + .bind(role.id_wolves) + .bind(role.id_role.get() as i64) + .bind(role.id_channel.get() as i64) + .bind(&role.name_role) + .bind(&role.name_channel) + .bind(role.count) + .fetch_optional(db) + .await + { + Ok(_) => {} + Err(e) => { + println!("Failure to insert into Wolves {:?}", role); + println!("{:?}", e); + } + } + } + + async fn db_roles_get(db: &Pool) -> Vec { + // expiry + sqlx::query_as::<_, CommitteeRoles>( + " + SELECT * + FROM committee_roles + ", + ) + .fetch_all(db) + .await + .unwrap_or_else(|e| { + println!("Failure to get Roles from committee_roles"); + println!("{:?}", e); + vec![] + }) + } + async fn get_committees(db: &Pool) -> Vec { sqlx::query_as::<_, Committees>( r#"