add file upload
parent
79a2f2839f
commit
752ce3f16c
|
|
@ -1,9 +1,12 @@
|
|||
use anyhow::{anyhow, Context};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::lib::domain::intro_tool::{
|
||||
models::guild::{self, GetUserError, GuildId, IntroId, User},
|
||||
ports::{IntroToolRepository, IntroToolService},
|
||||
use crate::{
|
||||
lib::domain::intro_tool::{
|
||||
models::guild::{self, GetUserError, GuildId, IntroId, User},
|
||||
ports::{IntroToolRepository, IntroToolService},
|
||||
},
|
||||
media,
|
||||
};
|
||||
|
||||
use super::models;
|
||||
|
|
@ -98,14 +101,28 @@ where
|
|||
req: guild::AddIntroToGuildRequest,
|
||||
) -> Result<IntroId, guild::AddIntroToGuildError> {
|
||||
let file_name = match &req.data {
|
||||
guild::IntroRequestData::Data(items) => todo!(),
|
||||
guild::IntroRequestData::Data(bytes) => {
|
||||
// TODO: put this behind an interface
|
||||
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, bytes).context("failed to write temp file")?;
|
||||
media::normalize(&temp_path, &dest_path)
|
||||
.await
|
||||
.context("failed to normalize file")?;
|
||||
std::fs::remove_file(&temp_path).context("failed to remove temp file")?;
|
||||
|
||||
dest_path
|
||||
}
|
||||
guild::IntroRequestData::Url(url) => {
|
||||
let uuid = Uuid::new_v4().to_string();
|
||||
let file_name = format!("sounds/{uuid}");
|
||||
|
||||
// TODO: put this behind an interface
|
||||
let child = tokio::process::Command::new("yt-dlp")
|
||||
.arg(&url)
|
||||
.arg(url)
|
||||
.args(["-o", &file_name])
|
||||
.args(["-x", "--audio-format", "mp3"])
|
||||
.spawn()
|
||||
|
|
|
|||
|
|
@ -117,6 +117,10 @@ where
|
|||
.route("/login", get(page::login))
|
||||
.route("/guild/:guild_id", get(page::guild_dashboard))
|
||||
.route("/v2/intros/:guild/add", get(handlers::add_guild_intro))
|
||||
.route(
|
||||
"/v2/intros/:guild/upload",
|
||||
post(handlers::upload_guild_intro),
|
||||
)
|
||||
|
||||
// .route("/guild/:guild_id/setup", get(routes::guild_setup))
|
||||
// .route(
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use axum::{
|
||||
extract::{Path, Query, State},
|
||||
extract::{Multipart, Path, Query, State},
|
||||
http::{HeaderMap, HeaderValue},
|
||||
};
|
||||
|
||||
|
|
@ -17,27 +17,33 @@ use crate::lib::{
|
|||
};
|
||||
|
||||
trait FromApi<T, P>: Sized {
|
||||
fn from_api(value: T, params: P) -> Result<Self, ApiError>;
|
||||
async fn from_api(value: T, params: P) -> Result<Self, ApiError>;
|
||||
}
|
||||
trait IntoDomain<T, P> {
|
||||
fn into_domain(self, params: P) -> Result<T, ApiError>;
|
||||
async fn into_domain(self, params: P) -> Result<T, ApiError>;
|
||||
}
|
||||
|
||||
impl<I, O: FromApi<I, P>, P> IntoDomain<O, P> for I {
|
||||
fn into_domain(self, params: P) -> Result<O, ApiError> {
|
||||
O::from_api(self, params)
|
||||
async fn into_domain(self, params: P) -> Result<O, ApiError> {
|
||||
O::from_api(self, params).await
|
||||
}
|
||||
}
|
||||
|
||||
impl FromApi<HashMap<String, String>, GuildId> for AddIntroToGuildRequest {
|
||||
fn from_api(value: HashMap<String, String>, params: GuildId) -> Result<Self, ApiError> {
|
||||
async fn from_api(value: HashMap<String, String>, params: GuildId) -> Result<Self, ApiError> {
|
||||
let Some(url) = value.get("url") else {
|
||||
return Err(ApiError::bad_request("url is required"));
|
||||
};
|
||||
if url.is_empty() {
|
||||
return Err(ApiError::bad_request("url cannot be empty"));
|
||||
}
|
||||
|
||||
let Some(name) = value.get("name") else {
|
||||
return Err(ApiError::bad_request("name is required"));
|
||||
};
|
||||
if name.is_empty() {
|
||||
return Err(ApiError::bad_request("name cannot be empty"));
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
guild_id: params,
|
||||
|
|
@ -48,13 +54,93 @@ impl FromApi<HashMap<String, String>, GuildId> for AddIntroToGuildRequest {
|
|||
}
|
||||
}
|
||||
|
||||
impl FromApi<Multipart, GuildId> for AddIntroToGuildRequest {
|
||||
async fn from_api(mut form_data: Multipart, params: GuildId) -> Result<Self, ApiError> {
|
||||
let mut 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") {
|
||||
name = Some(field.text().await.map_err(|err| {
|
||||
ApiError::bad_request(format!("expected text for name: {err:?}"))
|
||||
})?);
|
||||
continue;
|
||||
}
|
||||
|
||||
if field_name.eq_ignore_ascii_case("file") {
|
||||
file = Some(field.bytes().await.map_err(|err| {
|
||||
ApiError::bad_request(format!("expected bytes for file: {err:?}"))
|
||||
})?);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let Some(name) = name else {
|
||||
return Err(ApiError::bad_request("name is required"));
|
||||
};
|
||||
if name.is_empty() {
|
||||
return Err(ApiError::bad_request("name cannot be empty"));
|
||||
}
|
||||
|
||||
let Some(file) = file else {
|
||||
return Err(ApiError::bad_request("file is required"));
|
||||
};
|
||||
if file.is_empty() {
|
||||
return Err(ApiError::bad_request("file cannot be empty"));
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
guild_id: params,
|
||||
name: name.to_string(),
|
||||
volume: 0,
|
||||
data: IntroRequestData::Data(file.to_vec()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn add_guild_intro<S: IntroToolService>(
|
||||
State(state): State<ApiState<S>>,
|
||||
Path(guild_id): Path<u64>,
|
||||
Query(params): Query<HashMap<String, String>>,
|
||||
user: User,
|
||||
) -> Result<HeaderMap, ApiError> {
|
||||
let req = params.into_domain(guild_id.into())?;
|
||||
let req = params.into_domain(guild_id.into()).await?;
|
||||
|
||||
let guild = state.intro_tool_service.get_guild(guild_id).await?;
|
||||
let user_guilds = state
|
||||
.intro_tool_service
|
||||
.get_user_guilds(user.name())
|
||||
.await?;
|
||||
|
||||
// does user have access to this guild
|
||||
if !user_guilds
|
||||
.iter()
|
||||
.any(|guild_ref| guild_ref.id() == guild.id())
|
||||
{
|
||||
return Err(ApiError::forbidden(
|
||||
"You do not have access to this guild".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
state.intro_tool_service.add_intro_to_guild(req).await?;
|
||||
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert("HX-Refresh", HeaderValue::from_static("true"));
|
||||
|
||||
Ok(headers)
|
||||
}
|
||||
|
||||
pub(super) async fn upload_guild_intro<S: IntroToolService>(
|
||||
State(state): State<ApiState<S>>,
|
||||
Path(guild_id): Path<u64>,
|
||||
user: User,
|
||||
form_data: Multipart,
|
||||
) -> Result<HeaderMap, ApiError> {
|
||||
let req = form_data.into_domain(guild_id.into()).await?;
|
||||
|
||||
let guild = state.intro_tool_service.get_guild(guild_id).await?;
|
||||
let user_guilds = state
|
||||
|
|
|
|||
Loading…
Reference in New Issue