From 52d7cc7dedc6d27c867bf953fb781a81fc13e730 Mon Sep 17 00:00:00 2001 From: Patrick Cleavelin Date: Sun, 6 Aug 2023 17:12:26 -0500 Subject: [PATCH] support adding and removing intros --- src/db.rs | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++- src/page.rs | 23 +++++++++++--- src/routes.rs | 40 +++++++++++++++--------- 3 files changed, 128 insertions(+), 19 deletions(-) diff --git a/src/db.rs b/src/db.rs index dbf7fd2..a11bfed 100644 --- a/src/db.rs +++ b/src/db.rs @@ -2,7 +2,7 @@ use std::path::Path; use iter_tools::Itertools; use rusqlite::{Connection, Result}; -use tracing::error; +use tracing::{error, warn}; use crate::auth; @@ -136,6 +136,34 @@ impl Database { ) } + pub(crate) fn get_guild_channels(&self, guild_id: u64) -> Result> { + let mut query = self.conn.prepare( + " + SELECT + Channel.name + FROM Channel + WHERE + Channel.guild_id = :guild_id + ORDER BY Channel.name DESC + ", + )?; + + // NOTE(pcleavelin): for some reason this needs to be a let-binding or else + // the compiler complains about it being dropped too early (maybe I should update the compiler version) + let intros = query + .query_map( + &[ + // :vomit: + (":guild_id", &guild_id.to_string()), + ], + |row| Ok(row.get(0)?), + )? + .into_iter() + .collect::>>(); + + intros + } + pub(crate) fn get_user_channel_intros( &self, username: &str, @@ -151,6 +179,60 @@ impl Database { Ok(intros) } + + pub fn insert_user_intro( + &self, + username: &str, + guild_id: u64, + channel_name: &str, + intro_id: i32, + ) -> Result<()> { + let affected = self.conn.execute( + "INSERT INTO UserIntro (username, guild_id, channel_name, intro_id) VALUES (?1, ?2, ?3, ?4)", + &[ + username, + &guild_id.to_string(), + channel_name, + &intro_id.to_string(), + ], + )?; + + if affected < 1 { + warn!("no rows affected when attempting to insert user intro"); + } + + Ok(()) + } + + pub fn remove_user_intro( + &self, + username: &str, + guild_id: u64, + channel_name: &str, + intro_id: i32, + ) -> Result<()> { + let affected = self.conn.execute( + "DELETE FROM + UserIntro + WHERE + username = ?1 + AND guild_id = ?2 + AND channel_name = ?3 + AND intro_id = ?4", + &[ + username, + &guild_id.to_string(), + channel_name, + &intro_id.to_string(), + ], + )?; + + if affected < 1 { + warn!("no rows affected when attempting to delete user intro"); + } + + Ok(()) + } } pub struct Guild { diff --git a/src/page.rs b/src/page.rs index 4943bc2..6ce9166 100644 --- a/src/page.rs +++ b/src/page.rs @@ -111,6 +111,11 @@ pub(crate) async fn guild_dashboard( // TODO: change to actual error Redirect::to("/login") })?; + let guild_channels = db.get_guild_channels(guild_id).map_err(|err| { + error!(?err, %guild_id, "couldn't get guild channels"); + // TODO: change to actual error + Redirect::to("/login") + })?; let all_user_intros = db.get_all_user_intros(guild_id).map_err(|err| { error!(?err, %guild_id, "couldn't get user intros"); // TODO: change to actual error @@ -175,17 +180,27 @@ pub(crate) async fn guild_dashboard( .builder(Tag::Article, |b| { let mut b = b.builder_text(Tag::Header, "Guild Intros"); - for (channel_name, intros) in user_intros.into_iter() { + let mut user_intros = user_intros.into_iter().peekable(); + + for guild_channel_name in guild_channels { + // Get user intros for this channel + let intros = user_intros + .peeking_take_while(|(channel_name, _)| { + channel_name == &&guild_channel_name + }) + .map(|(_, intros)| intros.map(|intro| &intro.intro)) + .flatten(); + b = b.builder(Tag::Article, |b| { - b.builder_text(Tag::Header, &channel_name).builder( + b.builder_text(Tag::Header, &guild_channel_name).builder( Tag::Div, |b| { b.attribute("id", "channel-intro-selector") .push_builder(channel_intro_selector( &state.origin, guild_id, - channel_name, - intros.map(|intro| &intro.intro), + &guild_channel_name, + intros, guild_intros.iter(), )) }, diff --git a/src/routes.rs b/src/routes.rs index f843648..a30bf8e 100644 --- a/src/routes.rs +++ b/src/routes.rs @@ -342,21 +342,22 @@ pub(crate) async fn v2_add_intro_to_user( let db = state.db.lock().await; while let Ok(Some(field)) = form_data.next_field().await { - let Some(field_name) = field.name() else { + let Some(intro_id) = field.name() else { continue; }; - // TODO: insert into database - //if !channel_user - // .intros - // .iter() - // .any(|intro| intro.index == field_name) - //{ - // channel_user.intros.push(IntroIndex { - // index: field_name.to_string(), - // volume: 20, - // }); - //} + let intro_id = intro_id.parse::().map_err(|err| { + error!(?err, "invalid intro id"); + // TODO: change to actual error + Redirect::to("/login") + })?; + + db.insert_user_intro(&user.name, guild_id, &channel, intro_id) + .map_err(|err| { + error!(?err, "failed to add user intro"); + // TODO: change to actual error + Redirect::to("/login") + })?; } let guild_intros = db.get_guild_intros(guild_id).map_err(|err| { @@ -394,11 +395,22 @@ pub(crate) async fn v2_remove_intro_from_user( let db = state.db.lock().await; while let Ok(Some(field)) = form_data.next_field().await { - let Some(field_name) = field.name() else { + let Some(intro_id) = field.name() else { continue; }; - // TODO: remove from database + let intro_id = intro_id.parse::().map_err(|err| { + error!(?err, "invalid intro id"); + // TODO: change to actual error + Redirect::to("/login") + })?; + + db.remove_user_intro(&user.name, guild_id, &channel, intro_id) + .map_err(|err| { + error!(?err, "failed to remove user intro"); + // TODO: change to actual error + Redirect::to("/login") + })?; } let guild_intros = db.get_guild_intros(guild_id).map_err(|err| {