don't refresh on adding intro, swap widget
parent
ff7e608f9a
commit
5da57545e2
60
src/db.rs
60
src/db.rs
|
@ -42,19 +42,15 @@ impl Database {
|
|||
guilds
|
||||
}
|
||||
|
||||
pub fn get_all_user_intros(&self, username: &str, guild_id: u64) -> Result<Vec<Intro>> {
|
||||
pub fn get_guild_intros(&self, guild_id: u64) -> Result<Vec<Intro>> {
|
||||
let mut query = self.conn.prepare(
|
||||
"
|
||||
SELECT
|
||||
Intro.id,
|
||||
Intro.name,
|
||||
UI.channel_name
|
||||
Intro.name
|
||||
FROM Intro
|
||||
LEFT JOIN UserIntro UI ON UI.intro_id = Intro.id
|
||||
WHERE
|
||||
UI.username = :username
|
||||
AND UI.guild_id = :guild_id
|
||||
ORDER BY UI.channel_name DESC, UI.intro_id;
|
||||
Intro.guild_id = :guild_id
|
||||
",
|
||||
)?;
|
||||
|
||||
|
@ -63,7 +59,6 @@ impl Database {
|
|||
let intros = query
|
||||
.query_map(
|
||||
&[
|
||||
(":username", username),
|
||||
// :vomit:
|
||||
(":guild_id", &guild_id.to_string()),
|
||||
],
|
||||
|
@ -71,7 +66,6 @@ impl Database {
|
|||
Ok(Intro {
|
||||
id: row.get(0)?,
|
||||
name: row.get(1)?,
|
||||
channel_name: row.get(2)?,
|
||||
})
|
||||
},
|
||||
)?
|
||||
|
@ -81,6 +75,47 @@ impl Database {
|
|||
intros
|
||||
}
|
||||
|
||||
pub fn get_all_user_intros(&self, guild_id: u64) -> Result<Vec<UserIntro>> {
|
||||
let mut query = self.conn.prepare(
|
||||
"
|
||||
SELECT
|
||||
Intro.id,
|
||||
Intro.name,
|
||||
UI.channel_name,
|
||||
UI.username
|
||||
FROM Intro
|
||||
LEFT JOIN UserIntro UI ON UI.intro_id = Intro.id
|
||||
WHERE
|
||||
UI.guild_id = :guild_id
|
||||
ORDER BY UI.username DESC, UI.channel_name DESC, UI.intro_id;
|
||||
",
|
||||
)?;
|
||||
|
||||
// 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(UserIntro {
|
||||
intro: Intro {
|
||||
id: row.get(0)?,
|
||||
name: row.get(1)?,
|
||||
},
|
||||
channel_name: row.get(2)?,
|
||||
username: row.get(3)?,
|
||||
})
|
||||
},
|
||||
)?
|
||||
.into_iter()
|
||||
.collect::<Result<Vec<UserIntro>>>();
|
||||
|
||||
intros
|
||||
}
|
||||
|
||||
pub(crate) fn get_user_permissions(
|
||||
&self,
|
||||
username: &str,
|
||||
|
@ -109,5 +144,10 @@ pub struct Guild {
|
|||
pub struct Intro {
|
||||
pub id: i32,
|
||||
pub name: String,
|
||||
pub channel_name: String,
|
||||
}
|
||||
|
||||
pub struct UserIntro {
|
||||
pub intro: Intro,
|
||||
pub channel_name: String,
|
||||
pub username: String,
|
||||
}
|
||||
|
|
131
src/page.rs
131
src/page.rs
|
@ -77,6 +77,7 @@ fn intro_list<'a>(
|
|||
HtmxBuilder::new(Tag::Empty).form(|b| {
|
||||
b.attribute("class", "container")
|
||||
.hx_post(post)
|
||||
.hx_target("closest #channel-intro-selector")
|
||||
.attribute("hx-encoding", "multipart/form-data")
|
||||
.builder(Tag::FieldSet, |b| {
|
||||
let mut b = b
|
||||
|
@ -105,18 +106,24 @@ pub(crate) async fn guild_dashboard(
|
|||
) -> Result<Html<String>, Redirect> {
|
||||
let db = state.db.lock().await;
|
||||
|
||||
let user_intros = db
|
||||
.get_all_user_intros(&user.name, guild_id)
|
||||
.map_err(|err| {
|
||||
error!(?err, user = %user.name, %guild_id, "couldn't get user's intros");
|
||||
// TODO: change to actual error
|
||||
Redirect::to("/login")
|
||||
})?;
|
||||
let guild_intros = db.get_guild_intros(guild_id).map_err(|err| {
|
||||
error!(?err, %guild_id, "couldn't get guild intros");
|
||||
// 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
|
||||
Redirect::to("/login")
|
||||
})?;
|
||||
let user_permissions = db
|
||||
.get_user_permissions(&user.name, guild_id)
|
||||
.unwrap_or_default();
|
||||
|
||||
let channel_user_intros = user_intros.iter().group_by(|intro| &intro.channel_name);
|
||||
let grouped_intros = all_user_intros.iter().group_by(|intro| &intro.username);
|
||||
let user_intros = grouped_intros
|
||||
.into_iter()
|
||||
.filter(|(username, _)| username == &&user.name);
|
||||
|
||||
let can_upload = user_permissions.can(auth::Permission::UploadSounds);
|
||||
let is_moderator = user_permissions.can(auth::Permission::DeleteSounds);
|
||||
|
@ -168,74 +175,28 @@ 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 &channel_user_intros {
|
||||
b = b.builder(Tag::Article, |b| {
|
||||
b.builder_text(Tag::Header, &channel_name).builder(
|
||||
Tag::Div,
|
||||
|b| {
|
||||
b.builder_text(Tag::Strong, "Your Current Intros")
|
||||
.push_builder(intro_list(
|
||||
intros,
|
||||
"Remove Intro",
|
||||
&format!(
|
||||
"{}/v2/intros/remove/{}/{}",
|
||||
state.origin, guild_id, channel_name
|
||||
),
|
||||
))
|
||||
},
|
||||
)
|
||||
});
|
||||
for (_, intros) in user_intros {
|
||||
for (channel_name, intros) in
|
||||
intros.group_by(|intro| &intro.channel_name).into_iter()
|
||||
{
|
||||
b = b.builder(Tag::Article, |b| {
|
||||
b.builder_text(Tag::Header, &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_intros.iter(),
|
||||
))
|
||||
},
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// for (channel_name, channel_settings) in &guild.channels {
|
||||
// if let Some(channel_user) = channel_settings.users.get(&user.name) {
|
||||
// let current_intros =
|
||||
// channel_user.intros.iter().filter_map(|intro_index| {
|
||||
// Some((
|
||||
// &intro_index.index,
|
||||
// guild.intros.get(&intro_index.index)?,
|
||||
// ))
|
||||
// });
|
||||
// let available_intros =
|
||||
// guild.intros.iter().filter_map(|intro| {
|
||||
// if !channel_user
|
||||
// .intros
|
||||
// .iter()
|
||||
// .any(|intro_index| intro.0 == &intro_index.index)
|
||||
// {
|
||||
// Some((intro.0, intro.1))
|
||||
// } else {
|
||||
// None
|
||||
// }
|
||||
// });
|
||||
// b = b.builder(Tag::Article, |b| {
|
||||
// b.builder_text(Tag::Header, channel_name).builder(
|
||||
// Tag::Div,
|
||||
// |b| {
|
||||
// b.builder_text(Tag::Strong, "Your Current Intros")
|
||||
// .push_builder(intro_list(
|
||||
// current_intros,
|
||||
// "Remove Intro",
|
||||
// &format!(
|
||||
// "{}/v2/intros/remove/{}/{}",
|
||||
// state.origin, guild_id, channel_name
|
||||
// ),
|
||||
// ))
|
||||
// .builder_text(Tag::Strong, "Select Intros")
|
||||
// .push_builder(intro_list(
|
||||
// available_intros,
|
||||
// "Add Intro",
|
||||
// &format!(
|
||||
// "{}/v2/intros/add/{}/{}",
|
||||
// state.origin, guild_id, channel_name
|
||||
// ),
|
||||
// ))
|
||||
// },
|
||||
// )
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
|
||||
b
|
||||
})
|
||||
})
|
||||
|
@ -244,6 +205,28 @@ pub(crate) async fn guild_dashboard(
|
|||
))
|
||||
}
|
||||
|
||||
pub fn channel_intro_selector<'a>(
|
||||
origin: &str,
|
||||
guild_id: u64,
|
||||
channel_name: &String,
|
||||
intros: impl Iterator<Item = &'a db::Intro>,
|
||||
guild_intros: impl Iterator<Item = &'a db::Intro>,
|
||||
) -> HtmxBuilder {
|
||||
HtmxBuilder::new(Tag::Empty)
|
||||
.builder_text(Tag::Strong, "Your Current Intros")
|
||||
.push_builder(intro_list(
|
||||
intros,
|
||||
"Remove Intro",
|
||||
&format!("{}/v2/intros/remove/{}/{}", origin, guild_id, &channel_name),
|
||||
))
|
||||
.builder_text(Tag::Strong, "Select Intros")
|
||||
.push_builder(intro_list(
|
||||
guild_intros,
|
||||
"Add Intro",
|
||||
&format!("{}/v2/intros/add/{}/{}", origin, guild_id, channel_name),
|
||||
))
|
||||
}
|
||||
|
||||
fn upload_form(origin: &str, guild_id: u64) -> HtmxBuilder {
|
||||
HtmxBuilder::new(Tag::Empty).form(|b| {
|
||||
b.attribute("class", "container")
|
||||
|
|
|
@ -4,11 +4,12 @@ use axum::{
|
|||
body::Bytes,
|
||||
extract::{Multipart, Path, Query, State},
|
||||
http::{HeaderMap, HeaderValue},
|
||||
response::{IntoResponse, Redirect},
|
||||
response::{Html, IntoResponse, Redirect},
|
||||
Form, Json,
|
||||
};
|
||||
|
||||
use axum_extra::extract::{cookie::Cookie, CookieJar};
|
||||
use iter_tools::Itertools;
|
||||
use reqwest::{Proxy, StatusCode, Url};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{json, Value};
|
||||
|
@ -17,6 +18,8 @@ use uuid::Uuid;
|
|||
|
||||
use crate::{
|
||||
auth::{self, User},
|
||||
htmx::Build,
|
||||
page,
|
||||
settings::FileIntro,
|
||||
};
|
||||
use crate::{
|
||||
|
@ -89,6 +92,9 @@ pub(crate) enum Error {
|
|||
YtdlTerminated,
|
||||
#[error("ffmpeg terminated unsuccessfully")]
|
||||
FfmpegTerminated,
|
||||
|
||||
#[error("database error: {0}")]
|
||||
Database(#[from] rusqlite::Error),
|
||||
}
|
||||
|
||||
impl IntoResponse for Error {
|
||||
|
@ -111,6 +117,10 @@ impl IntoResponse for Error {
|
|||
Self::YtdlTerminated | Self::FfmpegTerminated => {
|
||||
(StatusCode::INTERNAL_SERVER_ERROR, self.to_string()).into_response()
|
||||
}
|
||||
|
||||
Self::Database(error) => {
|
||||
(StatusCode::INTERNAL_SERVER_ERROR, error.to_string()).into_response()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -328,45 +338,72 @@ pub(crate) async fn v2_add_intro_to_user(
|
|||
Path((guild_id, channel)): Path<(u64, String)>,
|
||||
user: User,
|
||||
mut form_data: Multipart,
|
||||
) -> HeaderMap {
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert("HX-Refresh", HeaderValue::from_static("true"));
|
||||
|
||||
let mut settings = state.settings.lock().await;
|
||||
|
||||
let Some(guild) = settings.guilds.get_mut(&guild_id) else {
|
||||
return headers;
|
||||
};
|
||||
let Some(channel) = guild.channels.get_mut(&channel) else {
|
||||
return headers;
|
||||
};
|
||||
let Some(channel_user) = channel.users.get_mut(&user.name) else {
|
||||
return headers;
|
||||
};
|
||||
) -> Result<Html<String>, Redirect> {
|
||||
let db = state.db.lock().await;
|
||||
|
||||
while let Ok(Some(field)) = form_data.next_field().await {
|
||||
let Some(field_name) = field.name() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if !channel_user
|
||||
.intros
|
||||
.iter()
|
||||
.any(|intro| intro.index == field_name)
|
||||
{
|
||||
channel_user.intros.push(IntroIndex {
|
||||
index: field_name.to_string(),
|
||||
volume: 20,
|
||||
});
|
||||
}
|
||||
// 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,
|
||||
// });
|
||||
//}
|
||||
}
|
||||
|
||||
// TODO: don't save on every change
|
||||
if let Err(err) = settings.save() {
|
||||
error!("Failed to save config: {err:?}");
|
||||
}
|
||||
let guild_intros = db.get_guild_intros(guild_id).map_err(|err| {
|
||||
error!(?err, %guild_id, "couldn't get guild intros");
|
||||
// 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
|
||||
Redirect::to("/login")
|
||||
})?;
|
||||
|
||||
headers
|
||||
let grouped_intros = all_user_intros.iter().group_by(|intro| &intro.username);
|
||||
let user_intros = grouped_intros
|
||||
.into_iter()
|
||||
.filter_map(|(username, intro)| {
|
||||
if username == &user.name {
|
||||
Some(intro)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.flatten();
|
||||
|
||||
let grouped_user_intros = user_intros.group_by(|intro| &intro.channel_name);
|
||||
let intros = grouped_user_intros
|
||||
.into_iter()
|
||||
.filter_map(|(channel_name, intros)| {
|
||||
if channel_name == &channel {
|
||||
Some(intros.map(|intro| &intro.intro))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.flatten();
|
||||
|
||||
Ok(Html(
|
||||
page::channel_intro_selector(
|
||||
&state.origin,
|
||||
guild_id,
|
||||
&channel,
|
||||
intros,
|
||||
guild_intros.iter(),
|
||||
)
|
||||
.build(),
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) async fn v2_remove_intro_from_user(
|
||||
|
|
Loading…
Reference in New Issue