parent
665e83a6fe
commit
9bedffa616
|
@ -46,7 +46,7 @@
|
||||||
packages = with pkgs; flake-utils.lib.flattenTree rec {
|
packages = with pkgs; flake-utils.lib.flattenTree rec {
|
||||||
default = rustPlatform.buildRustPackage rec {
|
default = rustPlatform.buildRustPackage rec {
|
||||||
name = "memejoin-rs";
|
name = "memejoin-rs";
|
||||||
version = "0.1.2-alpha";
|
version = "0.1.4-alpha";
|
||||||
src = self;
|
src = self;
|
||||||
buildInputs = [ openssl.dev ];
|
buildInputs = [ openssl.dev ];
|
||||||
nativeBuildInputs = [ local-rust pkg-config openssl openssl.dev cmake gcc libopus ];
|
nativeBuildInputs = [ local-rust pkg-config openssl openssl.dev cmake gcc libopus ];
|
||||||
|
@ -58,7 +58,7 @@
|
||||||
|
|
||||||
docker = dockerTools.buildImage {
|
docker = dockerTools.buildImage {
|
||||||
name = "memejoin-rs";
|
name = "memejoin-rs";
|
||||||
tag = "0.1.2-alpha";
|
tag = "0.1.4-alpha";
|
||||||
copyToRoot = buildEnv {
|
copyToRoot = buildEnv {
|
||||||
name = "image-root";
|
name = "image-root";
|
||||||
paths = [ default cacert openssl openssl.dev ffmpeg libopus youtube-dl yt-dlp ];
|
paths = [ default cacert openssl openssl.dev ffmpeg libopus youtube-dl yt-dlp ];
|
||||||
|
|
|
@ -39,7 +39,7 @@ impl Permissions {
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
pub enum Permission {
|
pub enum Permission {
|
||||||
None,
|
None,
|
||||||
DownloadSounds,
|
UploadSounds,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Permission {
|
impl Permission {
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
#![feature(async_closure)]
|
#![feature(async_closure)]
|
||||||
|
|
||||||
mod auth;
|
mod auth;
|
||||||
|
mod media;
|
||||||
mod routes;
|
mod routes;
|
||||||
pub mod settings;
|
pub mod settings;
|
||||||
|
|
||||||
|
@ -135,6 +136,7 @@ fn spawn_api(settings: Arc<Mutex<Settings>>) {
|
||||||
.route("/health", get(routes::health))
|
.route("/health", get(routes::health))
|
||||||
.route("/me", get(routes::me))
|
.route("/me", get(routes::me))
|
||||||
.route("/intros/:guild/add", get(routes::add_guild_intro))
|
.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", get(routes::intros))
|
||||||
.route(
|
.route(
|
||||||
"/intros/:guild/:channel/:intro",
|
"/intros/:guild/:channel/:intro",
|
||||||
|
|
|
@ -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(())
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
use std::{collections::HashMap, sync::Arc};
|
use std::{collections::HashMap, sync::Arc};
|
||||||
|
|
||||||
use axum::{
|
use axum::{
|
||||||
|
body::Bytes,
|
||||||
extract::{Path, Query, State},
|
extract::{Path, Query, State},
|
||||||
http::HeaderMap,
|
http::HeaderMap,
|
||||||
response::IntoResponse,
|
response::IntoResponse,
|
||||||
|
@ -13,8 +14,11 @@ use serde_json::{json, Value};
|
||||||
use tracing::{error, info};
|
use tracing::{error, info};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::settings::{ApiState, GuildUser, Intro, IntroIndex, UserSettings};
|
|
||||||
use crate::{auth, settings::FileIntro};
|
use crate::{auth, settings::FileIntro};
|
||||||
|
use crate::{
|
||||||
|
media,
|
||||||
|
settings::{ApiState, GuildUser, Intro, IntroIndex, UserSettings},
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
pub(crate) enum IntroResponse<'a> {
|
pub(crate) enum IntroResponse<'a> {
|
||||||
|
@ -71,9 +75,13 @@ pub(crate) enum Error {
|
||||||
InvalidPermission,
|
InvalidPermission,
|
||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
Ytdl(#[from] std::io::Error),
|
Ytdl(#[from] std::io::Error),
|
||||||
|
#[error("{0}")]
|
||||||
|
Ffmpeg(String),
|
||||||
|
|
||||||
#[error("ytdl terminated unsuccessfully")]
|
#[error("ytdl terminated unsuccessfully")]
|
||||||
YtdlTerminated,
|
YtdlTerminated,
|
||||||
|
#[error("ffmpeg terminated unsuccessfully")]
|
||||||
|
FfmpegTerminated,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoResponse for Error {
|
impl IntoResponse for Error {
|
||||||
|
@ -92,7 +100,8 @@ impl IntoResponse for Error {
|
||||||
Self::Ytdl(error) => {
|
Self::Ytdl(error) => {
|
||||||
(StatusCode::INTERNAL_SERVER_ERROR, error.to_string()).into_response()
|
(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()
|
(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
|
// TODO: don't save on every change
|
||||||
//if let Err(err) = settings.save() {
|
if let Err(err) = settings.save() {
|
||||||
// error!("Failed to save config: {err:?}");
|
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
|
// TODO: don't save on every change
|
||||||
//if let Err(err) = settings.save() {
|
if let Err(err) = settings.save() {
|
||||||
// error!("Failed to save config: {err:?}");
|
error!("Failed to save config: {err:?}");
|
||||||
//}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn intros(
|
pub(crate) async fn intros(
|
||||||
|
@ -294,6 +303,7 @@ pub(crate) async fn me(
|
||||||
g.1.users
|
g.1.users
|
||||||
// TODO: why must clone
|
// TODO: why must clone
|
||||||
.entry(username.clone())
|
.entry(username.clone())
|
||||||
|
// TODO: check if owner for permissions
|
||||||
.or_insert(Default::default());
|
.or_insert(Default::default());
|
||||||
|
|
||||||
let mut guild = MeGuild {
|
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(
|
pub(crate) async fn add_guild_intro(
|
||||||
State(state): State<Arc<ApiState>>,
|
State(state): State<Arc<ApiState>>,
|
||||||
Path(guild): Path<u64>,
|
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) };
|
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);
|
return Err(Error::InvalidPermission);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue