allow intro uploads

pull/5/head v0.1.4-alpha
Patrick Cleavelin 2023-03-08 21:29:49 -06:00
parent 665e83a6fe
commit 9bedffa616
5 changed files with 90 additions and 12 deletions

View File

@ -46,7 +46,7 @@
packages = with pkgs; flake-utils.lib.flattenTree rec {
default = rustPlatform.buildRustPackage rec {
name = "memejoin-rs";
version = "0.1.2-alpha";
version = "0.1.4-alpha";
src = self;
buildInputs = [ openssl.dev ];
nativeBuildInputs = [ local-rust pkg-config openssl openssl.dev cmake gcc libopus ];
@ -58,7 +58,7 @@
docker = dockerTools.buildImage {
name = "memejoin-rs";
tag = "0.1.2-alpha";
tag = "0.1.4-alpha";
copyToRoot = buildEnv {
name = "image-root";
paths = [ default cacert openssl openssl.dev ffmpeg libopus youtube-dl yt-dlp ];

View File

@ -39,7 +39,7 @@ impl Permissions {
#[repr(u8)]
pub enum Permission {
None,
DownloadSounds,
UploadSounds,
}
impl Permission {

View File

@ -3,6 +3,7 @@
#![feature(async_closure)]
mod auth;
mod media;
mod routes;
pub mod settings;
@ -135,6 +136,7 @@ fn spawn_api(settings: Arc<Mutex<Settings>>) {
.route("/health", get(routes::health))
.route("/me", get(routes::me))
.route("/intros/:guild/add", get(routes::add_guild_intro))
.route("/intros/:guild/upload", post(routes::upload_guild_intro))
.route("/intros/:guild", get(routes::intros))
.route(
"/intros/:guild/:channel/:intro",

20
src/media.rs Normal file
View File

@ -0,0 +1,20 @@
use crate::routes::Error;
pub(crate) async fn normalize(src: &str, dest: &str) -> Result<(), Error> {
let child = tokio::process::Command::new("ffmpeg")
.args(["-i", src])
.arg("-vn")
.args(["-map", "0:a"])
.arg(dest)
.spawn()
.map_err(|err| Error::Ffmpeg(err.to_string()))?
.wait()
.await
.map_err(|err| Error::Ffmpeg(err.to_string()))?;
if !child.success() {
return Err(Error::FfmpegTerminated);
}
Ok(())
}

View File

@ -1,6 +1,7 @@
use std::{collections::HashMap, sync::Arc};
use axum::{
body::Bytes,
extract::{Path, Query, State},
http::HeaderMap,
response::IntoResponse,
@ -13,8 +14,11 @@ use serde_json::{json, Value};
use tracing::{error, info};
use uuid::Uuid;
use crate::settings::{ApiState, GuildUser, Intro, IntroIndex, UserSettings};
use crate::{auth, settings::FileIntro};
use crate::{
media,
settings::{ApiState, GuildUser, Intro, IntroIndex, UserSettings},
};
#[derive(Serialize)]
pub(crate) enum IntroResponse<'a> {
@ -71,9 +75,13 @@ pub(crate) enum Error {
InvalidPermission,
#[error("{0}")]
Ytdl(#[from] std::io::Error),
#[error("{0}")]
Ffmpeg(String),
#[error("ytdl terminated unsuccessfully")]
YtdlTerminated,
#[error("ffmpeg terminated unsuccessfully")]
FfmpegTerminated,
}
impl IntoResponse for Error {
@ -92,7 +100,8 @@ impl IntoResponse for Error {
Self::Ytdl(error) => {
(StatusCode::INTERNAL_SERVER_ERROR, error.to_string()).into_response()
}
Self::YtdlTerminated => {
Self::Ffmpeg(error) => (StatusCode::INTERNAL_SERVER_ERROR, error).into_response(),
Self::YtdlTerminated | Self::FfmpegTerminated => {
(StatusCode::INTERNAL_SERVER_ERROR, self.to_string()).into_response()
}
}
@ -224,9 +233,9 @@ pub(crate) async fn add_intro_to_user(
});
// TODO: don't save on every change
//if let Err(err) = settings.save() {
// error!("Failed to save config: {err:?}");
//}
if let Err(err) = settings.save() {
error!("Failed to save config: {err:?}");
}
}
}
@ -255,9 +264,9 @@ pub(crate) async fn remove_intro_to_user(
}
// TODO: don't save on every change
//if let Err(err) = settings.save() {
// error!("Failed to save config: {err:?}");
//}
if let Err(err) = settings.save() {
error!("Failed to save config: {err:?}");
}
}
pub(crate) async fn intros(
@ -294,6 +303,7 @@ pub(crate) async fn me(
g.1.users
// TODO: why must clone
.entry(username.clone())
// TODO: check if owner for permissions
.or_insert(Default::default());
let mut guild = MeGuild {
@ -326,6 +336,52 @@ pub(crate) async fn me(
}
}
pub(crate) async fn upload_guild_intro(
State(state): State<Arc<ApiState>>,
Path(guild): Path<u64>,
Query(mut params): Query<HashMap<String, String>>,
headers: HeaderMap,
file: Bytes,
) -> Result<(), Error> {
let mut settings = state.settings.lock().await;
let Some(token) = headers.get("token").and_then(|v| v.to_str().ok()) else { return Err(Error::NoUserFound); };
let Some(friendly_name) = params.remove("name") else { return Err(Error::InvalidRequest); };
{
let Some(guild) = settings.guilds.get(&guild) else { return Err(Error::NoGuildFound); };
let auth_user = match settings.auth_users.get(token) {
Some(user) => user,
None => return Err(Error::NoUserFound),
};
let Some(guild_user) = guild.users.get(&auth_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,
}),
);
Ok(())
}
pub(crate) async fn add_guild_intro(
State(state): State<Arc<ApiState>>,
Path(guild): Path<u64>,
@ -346,7 +402,7 @@ pub(crate) async fn add_guild_intro(
};
let Some(guild_user) = guild.users.get(&auth_user.name) else { return Err(Error::NoUserFound) };
if !guild_user.permissions.can(auth::Permission::DownloadSounds) {
if !guild_user.permissions.can(auth::Permission::UploadSounds) {
return Err(Error::InvalidPermission);
}
}