diff --git a/src/main.rs b/src/main.rs index c0ce65b..cab4cee 100644 --- a/src/main.rs +++ b/src/main.rs @@ -139,6 +139,7 @@ fn spawn_api(settings: Arc>) { .route("/index.html", get(page::home)) .route("/login", get(page::login)) .route("/guild/:guild_id", get(page::guild_dashboard)) + .route("/v2/auth", get(routes::v2_auth)) .route( "/v2/intros/add/:guild_id/:channel", post(routes::v2_add_intro_to_user), @@ -147,6 +148,10 @@ fn spawn_api(settings: Arc>) { "/v2/intros/remove/:guild_id/:channel", post(routes::v2_remove_intro_from_user), ) + .route( + "/v2/intros/:guild/upload", + post(routes::v2_upload_guild_intro), + ) .route("/health", get(routes::health)) .route("/me", get(routes::me)) .route("/intros/:guild", get(routes::intros)) @@ -162,7 +167,6 @@ fn spawn_api(settings: Arc>) { post(routes::remove_intro_to_user), ) .route("/auth", get(routes::auth)) - .route("/v2/auth", get(routes::v2_auth)) .layer( CorsLayer::new() // TODO: move this to env variable diff --git a/src/page.rs b/src/page.rs index 0b5f3f4..6b3928d 100644 --- a/src/page.rs +++ b/src/page.rs @@ -111,6 +111,7 @@ pub(crate) async fn guild_dashboard( return Err(Redirect::to(&format!("{}/", state.origin))); }; + let can_upload = guild_user.permissions.can(auth::Permission::UploadSounds); let is_moderator = guild_user.permissions.can(auth::Permission::DeleteSounds); Ok(Html( @@ -124,7 +125,7 @@ pub(crate) async fn guild_dashboard( }) }) .builder(Tag::Empty, |b| { - if is_moderator { + let mut b = if is_moderator { b.builder(Tag::Div, |b| { b.attribute("class", "container") .builder(Tag::Article, |b| { @@ -135,8 +136,20 @@ pub(crate) async fn guild_dashboard( }) } else { b - } - .builder(Tag::Div, |b| { + }; + b = if can_upload { + b.builder(Tag::Div, |b| { + b.attribute("class", "container") + .builder(Tag::Article, |b| { + b.builder_text(Tag::Header, "Upload New Intro") + .push_builder(upload_form(&state.origin, guild_id)) + }) + }) + } else { + b + }; + + b.builder(Tag::Div, |b| { b.attribute("class", "container") .builder(Tag::Article, |b| { let mut b = b.builder_text(Tag::Header, "Guild Intros"); @@ -198,6 +211,26 @@ pub(crate) async fn guild_dashboard( )) } +fn upload_form(origin: &str, guild_id: u64) -> HtmxBuilder { + HtmxBuilder::new(Tag::Empty).form(|b| { + b.attribute("class", "container") + .hx_post(&format!("{}/v2/intros/{}/upload", origin, guild_id)) + .attribute("hx-encoding", "multipart/form-data") + .builder(Tag::FieldSet, |b| { + b.attribute("class", "container") + .input(|b| { + b.attribute("name", "name") + .attribute("placeholder", "enter intro title") + }) + .label(|b| { + b.text("Choose File") + .input(|b| b.attribute("type", "file").attribute("name", "file")) + }) + }) + .button(|b| b.attribute("type", "submit").text("Upload")) + }) +} + fn moderator_dashboard(state: &ApiState) -> HtmxBuilder { HtmxBuilder::new(Tag::Empty).link("Go back to old UI", &format!("{}/old", state.origin)) } diff --git a/src/routes.rs b/src/routes.rs index 92a22b5..96cdd5b 100644 --- a/src/routes.rs +++ b/src/routes.rs @@ -611,6 +611,77 @@ pub(crate) async fn upload_guild_intro( Ok(()) } +pub(crate) async fn v2_upload_guild_intro( + State(state): State, + Path(guild): Path, + user: User, + mut form_data: Multipart, +) -> Result { + let mut settings = state.settings.lock().await; + let mut friendly_name = None; + let mut file = None; + + while let Ok(Some(field)) = form_data.next_field().await { + let Some(field_name) = field.name() else { + continue; + }; + + if field_name.eq_ignore_ascii_case("name") { + friendly_name = Some(field.text().await.map_err(|_| Error::InvalidRequest)?); + continue; + } + + if field_name.eq_ignore_ascii_case("file") { + file = Some(field.bytes().await.map_err(|_| Error::InvalidRequest)?); + continue; + } + } + + let Some(friendly_name) = friendly_name else { + return Err(Error::InvalidRequest); + }; + let Some(file) = file else { + return Err(Error::InvalidRequest); + }; + + { + let Some(guild) = settings.guilds.get(&guild) else { + return Err(Error::NoGuildFound); + }; + let Some(guild_user) = guild.users.get(&user.name) else { + return Err(Error::NoUserFound); + }; + + if !guild_user.permissions.can(auth::Permission::UploadSounds) { + return Err(Error::InvalidPermission); + } + } + + let Some(guild) = settings.guilds.get_mut(&guild) else { + return Err(Error::NoGuildFound); + }; + let uuid = Uuid::new_v4().to_string(); + let temp_path = format!("./sounds/temp/{uuid}"); + let dest_path = format!("./sounds/{uuid}.mp3"); + + // Write original file so its ready for codec conversion + std::fs::write(&temp_path, file)?; + media::normalize(&temp_path, &dest_path).await?; + std::fs::remove_file(&temp_path)?; + + guild.intros.insert( + uuid.clone(), + Intro::File(FileIntro { + filename: format!("{uuid}.mp3"), + friendly_name, + }), + ); + + let mut headers = HeaderMap::new(); + headers.insert("HX-Refresh", HeaderValue::from_static("true")); + Ok(headers) +} + pub(crate) async fn add_guild_intro( State(state): State, Path(guild): Path,