Compare commits
No commits in common. "52d7cc7dedc6d27c867bf953fb781a81fc13e730" and "969a97cab783b7f1d00e786bdc99d5cf1f3e775f" have entirely different histories.
52d7cc7ded
...
969a97cab7
|
@ -492,12 +492,6 @@ version = "0.15.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f"
|
checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "either"
|
|
||||||
version = "1.9.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "encoding_rs"
|
name = "encoding_rs"
|
||||||
version = "0.8.32"
|
version = "0.8.32"
|
||||||
|
@ -973,24 +967,6 @@ version = "2.8.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6"
|
checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "iter_tools"
|
|
||||||
version = "0.1.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "531cafdc99b3b3252bb32f5620e61d56b19415efc19900b12d1b2e7483854897"
|
|
||||||
dependencies = [
|
|
||||||
"itertools",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "itertools"
|
|
||||||
version = "0.10.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
|
|
||||||
dependencies = [
|
|
||||||
"either",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.9"
|
version = "1.0.9"
|
||||||
|
@ -1097,7 +1073,6 @@ dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"dotenv",
|
"dotenv",
|
||||||
"futures",
|
"futures",
|
||||||
"iter_tools",
|
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"rusqlite",
|
"rusqlite",
|
||||||
"serde",
|
"serde",
|
||||||
|
|
|
@ -12,7 +12,6 @@ axum-extra = { version = "0.7.5", features = ["cookie-private", "cookie"] }
|
||||||
chrono = "0.4.23"
|
chrono = "0.4.23"
|
||||||
dotenv = "0.15.0"
|
dotenv = "0.15.0"
|
||||||
futures = "0.3.26"
|
futures = "0.3.26"
|
||||||
iter_tools = "0.1.4"
|
|
||||||
reqwest = "0.11.14"
|
reqwest = "0.11.14"
|
||||||
rusqlite = { version = "0.29.0", features = ["bundled"] }
|
rusqlite = { version = "0.29.0", features = ["bundled"] }
|
||||||
serde = "1.0.152"
|
serde = "1.0.152"
|
||||||
|
|
207
src/db.rs
207
src/db.rs
|
@ -1,10 +1,6 @@
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use iter_tools::Itertools;
|
|
||||||
use rusqlite::{Connection, Result};
|
use rusqlite::{Connection, Result};
|
||||||
use tracing::{error, warn};
|
|
||||||
|
|
||||||
use crate::auth;
|
|
||||||
|
|
||||||
pub struct Database {
|
pub struct Database {
|
||||||
conn: Connection,
|
conn: Connection,
|
||||||
|
@ -21,7 +17,7 @@ impl Database {
|
||||||
let mut query = self.conn.prepare(
|
let mut query = self.conn.prepare(
|
||||||
"
|
"
|
||||||
SELECT
|
SELECT
|
||||||
id, name, soundDelay
|
id, name, sound_delay
|
||||||
FROM Guild
|
FROM Guild
|
||||||
LEFT JOIN UserGuild ON UserGuild.guild_id = Guild.id
|
LEFT JOIN UserGuild ON UserGuild.guild_id = Guild.id
|
||||||
WHERE UserGuild.username = :username
|
WHERE UserGuild.username = :username
|
||||||
|
@ -43,196 +39,6 @@ impl Database {
|
||||||
|
|
||||||
guilds
|
guilds
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_guild_intros(&self, guild_id: u64) -> Result<Vec<Intro>> {
|
|
||||||
let mut query = self.conn.prepare(
|
|
||||||
"
|
|
||||||
SELECT
|
|
||||||
Intro.id,
|
|
||||||
Intro.name
|
|
||||||
FROM Intro
|
|
||||||
WHERE
|
|
||||||
Intro.guild_id = :guild_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(Intro {
|
|
||||||
id: row.get(0)?,
|
|
||||||
name: row.get(1)?,
|
|
||||||
})
|
|
||||||
},
|
|
||||||
)?
|
|
||||||
.into_iter()
|
|
||||||
.collect::<Result<Vec<Intro>>>();
|
|
||||||
|
|
||||||
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,
|
|
||||||
guild_id: u64,
|
|
||||||
) -> Result<auth::Permissions> {
|
|
||||||
self.conn.query_row(
|
|
||||||
"
|
|
||||||
SELECT
|
|
||||||
permissions
|
|
||||||
FROM UserPermission
|
|
||||||
WHERE
|
|
||||||
username = ?1
|
|
||||||
",
|
|
||||||
[username],
|
|
||||||
|row| Ok(auth::Permissions(row.get(0)?)),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn get_guild_channels(&self, guild_id: u64) -> Result<Vec<String>> {
|
|
||||||
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::<Result<Vec<String>>>();
|
|
||||||
|
|
||||||
intros
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn get_user_channel_intros(
|
|
||||||
&self,
|
|
||||||
username: &str,
|
|
||||||
guild_id: u64,
|
|
||||||
channel_name: &str,
|
|
||||||
) -> Result<Vec<Intro>> {
|
|
||||||
let all_user_intros = self.get_all_user_intros(guild_id)?.into_iter();
|
|
||||||
|
|
||||||
let intros = all_user_intros
|
|
||||||
.filter(|intro| &intro.username == &username && &intro.channel_name == channel_name)
|
|
||||||
.map(|intro| intro.intro)
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
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 {
|
pub struct Guild {
|
||||||
|
@ -240,14 +46,3 @@ pub struct Guild {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub sound_delay: u32,
|
pub sound_delay: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Intro {
|
|
||||||
pub id: i32,
|
|
||||||
pub name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct UserIntro {
|
|
||||||
pub intro: Intro,
|
|
||||||
pub channel_name: String,
|
|
||||||
pub username: String,
|
|
||||||
}
|
|
||||||
|
|
141
src/page.rs
141
src/page.rs
|
@ -8,7 +8,6 @@ use axum::{
|
||||||
extract::{Path, State},
|
extract::{Path, State},
|
||||||
response::{Html, Redirect},
|
response::{Html, Redirect},
|
||||||
};
|
};
|
||||||
use iter_tools::Itertools;
|
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
|
|
||||||
fn page_header(title: &str) -> HtmxBuilder {
|
fn page_header(title: &str) -> HtmxBuilder {
|
||||||
|
@ -70,14 +69,13 @@ fn guild_list<'a>(origin: &str, guilds: impl Iterator<Item = &'a db::Guild>) ->
|
||||||
}
|
}
|
||||||
|
|
||||||
fn intro_list<'a>(
|
fn intro_list<'a>(
|
||||||
intros: impl Iterator<Item = &'a db::Intro>,
|
intros: impl Iterator<Item = (&'a String, &'a Intro)>,
|
||||||
label: &str,
|
label: &str,
|
||||||
post: &str,
|
post: &str,
|
||||||
) -> HtmxBuilder {
|
) -> HtmxBuilder {
|
||||||
HtmxBuilder::new(Tag::Empty).form(|b| {
|
HtmxBuilder::new(Tag::Empty).form(|b| {
|
||||||
b.attribute("class", "container")
|
b.attribute("class", "container")
|
||||||
.hx_post(post)
|
.hx_post(post)
|
||||||
.hx_target("closest #channel-intro-selector")
|
|
||||||
.attribute("hx-encoding", "multipart/form-data")
|
.attribute("hx-encoding", "multipart/form-data")
|
||||||
.builder(Tag::FieldSet, |b| {
|
.builder(Tag::FieldSet, |b| {
|
||||||
let mut b = b
|
let mut b = b
|
||||||
|
@ -86,10 +84,9 @@ fn intro_list<'a>(
|
||||||
for intro in intros {
|
for intro in intros {
|
||||||
b = b.builder(Tag::Label, |b| {
|
b = b.builder(Tag::Label, |b| {
|
||||||
b.builder(Tag::Input, |b| {
|
b.builder(Tag::Input, |b| {
|
||||||
b.attribute("type", "checkbox")
|
b.attribute("type", "checkbox").attribute("name", &intro.0)
|
||||||
.attribute("name", &intro.id.to_string())
|
|
||||||
})
|
})
|
||||||
.builder_text(Tag::Paragraph, &intro.name)
|
.builder_text(Tag::Paragraph, intro.1.friendly_name())
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,34 +101,19 @@ pub(crate) async fn guild_dashboard(
|
||||||
user: User,
|
user: User,
|
||||||
Path(guild_id): Path<u64>,
|
Path(guild_id): Path<u64>,
|
||||||
) -> Result<Html<String>, Redirect> {
|
) -> Result<Html<String>, Redirect> {
|
||||||
let db = state.db.lock().await;
|
let settings = state.settings.lock().await;
|
||||||
|
|
||||||
let guild_intros = db.get_guild_intros(guild_id).map_err(|err| {
|
let Some(guild) = settings.guilds.get(&guild_id) else {
|
||||||
error!(?err, %guild_id, "couldn't get guild intros");
|
error!(%guild_id, "no such guild");
|
||||||
// TODO: change to actual error
|
return Err(Redirect::to(&format!("{}/", state.origin)));
|
||||||
Redirect::to("/login")
|
};
|
||||||
})?;
|
let Some(guild_user) = guild.users.get(&user.name) else {
|
||||||
let guild_channels = db.get_guild_channels(guild_id).map_err(|err| {
|
error!(%guild_id, %user.name, "no user in guild");
|
||||||
error!(?err, %guild_id, "couldn't get guild channels");
|
return Err(Redirect::to(&format!("{}/", state.origin)));
|
||||||
// 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 user_intros = all_user_intros
|
let can_upload = guild_user.permissions.can(auth::Permission::UploadSounds);
|
||||||
.iter()
|
let is_moderator = guild_user.permissions.can(auth::Permission::DeleteSounds);
|
||||||
.filter(|intro| &intro.username == &user.name)
|
|
||||||
.group_by(|intro| &intro.channel_name);
|
|
||||||
|
|
||||||
let can_upload = user_permissions.can(auth::Permission::UploadSounds);
|
|
||||||
let is_moderator = user_permissions.can(auth::Permission::DeleteSounds);
|
|
||||||
|
|
||||||
Ok(Html(
|
Ok(Html(
|
||||||
HtmxBuilder::new(Tag::Html)
|
HtmxBuilder::new(Tag::Html)
|
||||||
|
@ -180,32 +162,53 @@ pub(crate) async fn guild_dashboard(
|
||||||
.builder(Tag::Article, |b| {
|
.builder(Tag::Article, |b| {
|
||||||
let mut b = b.builder_text(Tag::Header, "Guild Intros");
|
let mut b = b.builder_text(Tag::Header, "Guild Intros");
|
||||||
|
|
||||||
let mut user_intros = user_intros.into_iter().peekable();
|
for (channel_name, channel_settings) in &guild.channels {
|
||||||
|
if let Some(channel_user) = channel_settings.users.get(&user.name) {
|
||||||
for guild_channel_name in guild_channels {
|
let current_intros =
|
||||||
// Get user intros for this channel
|
channel_user.intros.iter().filter_map(|intro_index| {
|
||||||
let intros = user_intros
|
Some((
|
||||||
.peeking_take_while(|(channel_name, _)| {
|
&intro_index.index,
|
||||||
channel_name == &&guild_channel_name
|
guild.intros.get(&intro_index.index)?,
|
||||||
})
|
))
|
||||||
.map(|(_, intros)| intros.map(|intro| &intro.intro))
|
});
|
||||||
.flatten();
|
let available_intros =
|
||||||
|
guild.intros.iter().filter_map(|intro| {
|
||||||
b = b.builder(Tag::Article, |b| {
|
if !channel_user
|
||||||
b.builder_text(Tag::Header, &guild_channel_name).builder(
|
.intros
|
||||||
Tag::Div,
|
.iter()
|
||||||
|b| {
|
.any(|intro_index| intro.0 == &intro_index.index)
|
||||||
b.attribute("id", "channel-intro-selector")
|
{
|
||||||
.push_builder(channel_intro_selector(
|
Some((intro.0, intro.1))
|
||||||
&state.origin,
|
} else {
|
||||||
guild_id,
|
None
|
||||||
&guild_channel_name,
|
}
|
||||||
intros,
|
});
|
||||||
guild_intros.iter(),
|
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
|
b
|
||||||
|
@ -216,28 +219,6 @@ 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 {
|
fn upload_form(origin: &str, guild_id: u64) -> HtmxBuilder {
|
||||||
HtmxBuilder::new(Tag::Empty).form(|b| {
|
HtmxBuilder::new(Tag::Empty).form(|b| {
|
||||||
b.attribute("class", "container")
|
b.attribute("class", "container")
|
||||||
|
|
147
src/routes.rs
147
src/routes.rs
|
@ -4,12 +4,11 @@ use axum::{
|
||||||
body::Bytes,
|
body::Bytes,
|
||||||
extract::{Multipart, Path, Query, State},
|
extract::{Multipart, Path, Query, State},
|
||||||
http::{HeaderMap, HeaderValue},
|
http::{HeaderMap, HeaderValue},
|
||||||
response::{Html, IntoResponse, Redirect},
|
response::{IntoResponse, Redirect},
|
||||||
Form, Json,
|
Form, Json,
|
||||||
};
|
};
|
||||||
|
|
||||||
use axum_extra::extract::{cookie::Cookie, CookieJar};
|
use axum_extra::extract::{cookie::Cookie, CookieJar};
|
||||||
use iter_tools::Itertools;
|
|
||||||
use reqwest::{Proxy, StatusCode, Url};
|
use reqwest::{Proxy, StatusCode, Url};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::{json, Value};
|
use serde_json::{json, Value};
|
||||||
|
@ -18,8 +17,6 @@ use uuid::Uuid;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
auth::{self, User},
|
auth::{self, User},
|
||||||
htmx::Build,
|
|
||||||
page,
|
|
||||||
settings::FileIntro,
|
settings::FileIntro,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -92,9 +89,6 @@ pub(crate) enum Error {
|
||||||
YtdlTerminated,
|
YtdlTerminated,
|
||||||
#[error("ffmpeg terminated unsuccessfully")]
|
#[error("ffmpeg terminated unsuccessfully")]
|
||||||
FfmpegTerminated,
|
FfmpegTerminated,
|
||||||
|
|
||||||
#[error("database error: {0}")]
|
|
||||||
Database(#[from] rusqlite::Error),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoResponse for Error {
|
impl IntoResponse for Error {
|
||||||
|
@ -117,10 +111,6 @@ impl IntoResponse for Error {
|
||||||
Self::YtdlTerminated | Self::FfmpegTerminated => {
|
Self::YtdlTerminated | Self::FfmpegTerminated => {
|
||||||
(StatusCode::INTERNAL_SERVER_ERROR, self.to_string()).into_response()
|
(StatusCode::INTERNAL_SERVER_ERROR, self.to_string()).into_response()
|
||||||
}
|
}
|
||||||
|
|
||||||
Self::Database(error) => {
|
|
||||||
(StatusCode::INTERNAL_SERVER_ERROR, error.to_string()).into_response()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -338,52 +328,45 @@ pub(crate) async fn v2_add_intro_to_user(
|
||||||
Path((guild_id, channel)): Path<(u64, String)>,
|
Path((guild_id, channel)): Path<(u64, String)>,
|
||||||
user: User,
|
user: User,
|
||||||
mut form_data: Multipart,
|
mut form_data: Multipart,
|
||||||
) -> Result<Html<String>, Redirect> {
|
) -> HeaderMap {
|
||||||
let db = state.db.lock().await;
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
while let Ok(Some(field)) = form_data.next_field().await {
|
while let Ok(Some(field)) = form_data.next_field().await {
|
||||||
let Some(intro_id) = field.name() else {
|
let Some(field_name) = field.name() else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
let intro_id = intro_id.parse::<i32>().map_err(|err| {
|
if !channel_user
|
||||||
error!(?err, "invalid intro id");
|
.intros
|
||||||
// TODO: change to actual error
|
.iter()
|
||||||
Redirect::to("/login")
|
.any(|intro| intro.index == field_name)
|
||||||
})?;
|
{
|
||||||
|
channel_user.intros.push(IntroIndex {
|
||||||
db.insert_user_intro(&user.name, guild_id, &channel, intro_id)
|
index: field_name.to_string(),
|
||||||
.map_err(|err| {
|
volume: 20,
|
||||||
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| {
|
// TODO: don't save on every change
|
||||||
error!(?err, %guild_id, "couldn't get guild intros");
|
if let Err(err) = settings.save() {
|
||||||
// TODO: change to actual error
|
error!("Failed to save config: {err:?}");
|
||||||
Redirect::to("/login")
|
}
|
||||||
})?;
|
|
||||||
|
|
||||||
let intros = db
|
headers
|
||||||
.get_user_channel_intros(&user.name, guild_id, &channel)
|
|
||||||
.map_err(|err| {
|
|
||||||
error!(?err, user = %user.name, %guild_id, "couldn't get user intros");
|
|
||||||
// TODO: change to actual error
|
|
||||||
Redirect::to("/login")
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(Html(
|
|
||||||
page::channel_intro_selector(
|
|
||||||
&state.origin,
|
|
||||||
guild_id,
|
|
||||||
&channel,
|
|
||||||
intros.iter(),
|
|
||||||
guild_intros.iter(),
|
|
||||||
)
|
|
||||||
.build(),
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn v2_remove_intro_from_user(
|
pub(crate) async fn v2_remove_intro_from_user(
|
||||||
|
@ -391,52 +374,42 @@ pub(crate) async fn v2_remove_intro_from_user(
|
||||||
Path((guild_id, channel)): Path<(u64, String)>,
|
Path((guild_id, channel)): Path<(u64, String)>,
|
||||||
user: User,
|
user: User,
|
||||||
mut form_data: Multipart,
|
mut form_data: Multipart,
|
||||||
) -> Result<Html<String>, Redirect> {
|
) -> HeaderMap {
|
||||||
let db = state.db.lock().await;
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
while let Ok(Some(field)) = form_data.next_field().await {
|
while let Ok(Some(field)) = form_data.next_field().await {
|
||||||
let Some(intro_id) = field.name() else {
|
let Some(field_name) = field.name() else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
let intro_id = intro_id.parse::<i32>().map_err(|err| {
|
if let Some(index) = channel_user
|
||||||
error!(?err, "invalid intro id");
|
.intros
|
||||||
// TODO: change to actual error
|
.iter()
|
||||||
Redirect::to("/login")
|
.position(|intro| intro.index == field_name)
|
||||||
})?;
|
{
|
||||||
|
channel_user.intros.remove(index);
|
||||||
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| {
|
// TODO: don't save on every change
|
||||||
error!(?err, %guild_id, "couldn't get guild intros");
|
if let Err(err) = settings.save() {
|
||||||
// TODO: change to actual error
|
error!("Failed to save config: {err:?}");
|
||||||
Redirect::to("/login")
|
}
|
||||||
})?;
|
|
||||||
|
|
||||||
let intros = db
|
headers
|
||||||
.get_user_channel_intros(&user.name, guild_id, &channel)
|
|
||||||
.map_err(|err| {
|
|
||||||
error!(?err, user = %user.name, %guild_id, "couldn't get user intros");
|
|
||||||
// TODO: change to actual error
|
|
||||||
Redirect::to("/login")
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(Html(
|
|
||||||
page::channel_intro_selector(
|
|
||||||
&state.origin,
|
|
||||||
guild_id,
|
|
||||||
&channel,
|
|
||||||
intros.iter(),
|
|
||||||
guild_intros.iter(),
|
|
||||||
)
|
|
||||||
.build(),
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn add_intro_to_user(
|
pub(crate) async fn add_intro_to_user(
|
||||||
|
|
Loading…
Reference in New Issue