diff --git a/Cargo.toml b/Cargo.toml index 568466d..e1f7a85 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,6 @@ diesel = { version = "2.2", features = ["uuid"] } diesel-async = { version = "0.5", features = ["deadpool", "postgres", "async-connection-wrapper"] } diesel_migrations = { version = "2.2.0", features = ["postgres"] } thiserror = "2.0.12" -actix-multipart = "0.7.2" [dependencies.tokio] version = "1.44" diff --git a/src/api/v1/users/me.rs b/src/api/v1/users/me.rs index 9647002..2cefa4f 100644 --- a/src/api/v1/users/me.rs +++ b/src/api/v1/users/me.rs @@ -1,5 +1,4 @@ use actix_web::{get, patch, web, HttpRequest, HttpResponse}; -use actix_multipart::form::{json::Json as MpJson, tempfile::TempFile, MultipartForm}; use serde::Deserialize; use crate::{error::Error, structs::Me, api::v1::auth::check_access_token, utils::get_auth_header, Data}; @@ -19,7 +18,7 @@ pub async fn res(req: HttpRequest, data: web::Data) -> Result, display_name: Option, @@ -27,15 +26,8 @@ struct NewInfo { email: Option, } -#[derive(Debug, MultipartForm)] -struct UploadForm { - #[multipart(limit = "100MB")] - avatar: Option, - json: Option>, -} - #[patch("/me")] -pub async fn update(req: HttpRequest, MultipartForm(form): MultipartForm, data: web::Data) -> Result { +pub async fn update(req: HttpRequest, new_info: web::Json, data: web::Data) -> Result { let headers = req.headers(); let auth_header = get_auth_header(headers)?; @@ -44,32 +36,22 @@ pub async fn update(req: HttpRequest, MultipartForm(form): MultipartForm Scope { web::scope("/users") .service(res) .service(me::res) - .service(me::update) .service(uuid::res) } diff --git a/src/error.rs b/src/error.rs index 47415c0..8843b22 100644 --- a/src/error.rs +++ b/src/error.rs @@ -10,7 +10,7 @@ use diesel_async::pooled_connection::PoolError as DieselPoolError; use tokio::task::JoinError; use serde_json::Error as JsonError; use toml::de::Error as TomlError; -use log::{debug, error}; +use log::error; #[derive(Debug, Error)] pub enum Error { @@ -54,7 +54,6 @@ pub enum Error { impl ResponseError for Error { fn error_response(&self) -> HttpResponse { - debug!("{:?}", self); error!("{}: {}", self.status_code(), self.to_string()); HttpResponse::build(self.status_code()) diff --git a/src/structs.rs b/src/structs.rs index e593996..371f623 100644 --- a/src/structs.rs +++ b/src/structs.rs @@ -5,8 +5,9 @@ use diesel_async::{pooled_connection::AsyncDieselConnectionManager, RunQueryDsl} use tokio::task; use url::Url; use actix_web::web::BytesMut; +use bindet::FileType; -use crate::{error::Error, schema::*, utils::image_check, Conn, Data}; +use crate::{error::Error, Conn, Data, schema::*}; fn load_or_empty(query_result: Result, diesel::result::Error>) -> Result, diesel::result::Error> { match query_result { @@ -255,7 +256,7 @@ impl GuildBuilder { uuid: self.uuid, name: self.name, description: self.description, - icon: self.icon.and_then(|i| i.parse().ok()), + icon: self.icon, owner_uuid: self.owner_uuid, roles: roles, member_count: member_count, @@ -268,7 +269,7 @@ pub struct Guild { pub uuid: Uuid, name: String, description: Option, - icon: Option, + icon: Option, owner_uuid: Uuid, pub roles: Vec, member_count: i64, @@ -409,13 +410,29 @@ impl Guild { // FIXME: Horrible security pub async fn set_icon(&mut self, bunny_cdn: &bunny_api_tokio::Client, conn: &mut Conn, cdn_url: Url, icon: BytesMut) -> Result<(), Error> { - let icon_clone = icon.clone(); - let image_type = task::spawn_blocking(move || image_check(icon_clone)).await??; + let ico = icon.clone(); + + let image_type = task::spawn_blocking(move || { + let buf = std::io::Cursor::new(ico.to_vec()); + + let detect = bindet::detect(buf).map_err(|e| e.kind()); + + if let Ok(Some(file_type)) = detect { + if file_type.likely_to_be == vec![FileType::Jpg] { + return String::from("jpg") + } else if file_type.likely_to_be == vec![FileType::Png] { + return String::from("png") + } + } + String::from("unknown") + }).await?; + + if image_type == "unknown" { + return Err(Error::BadRequest("Not an image".to_string())) + } if let Some(icon) = &self.icon { - let relative_url = icon - .path() - .trim_start_matches('/'); + let relative_url = icon.trim_start_matches("https://cdn.gorb.app/"); bunny_cdn.storage.delete(relative_url).await?; } @@ -433,7 +450,7 @@ impl Guild { .execute(conn) .await?; - self.icon = Some(icon_url); + self.icon = Some(icon_url.to_string()); Ok(()) } @@ -667,38 +684,6 @@ impl Me { Ok(me) } - - pub async fn set_avatar(&mut self, bunny_cdn: &bunny_api_tokio::Client, conn: &mut Conn, cdn_url: Url, avatar: BytesMut) -> Result<(), Error> { - let avatar_clone = avatar.clone(); - let image_type = task::spawn_blocking(move || image_check(avatar_clone)).await??; - - if let Some(avatar) = &self.avatar { - let avatar_url: Url = avatar.parse()?; - - let relative_url = avatar_url - .path() - .trim_start_matches('/'); - - bunny_cdn.storage.delete(relative_url).await?; - } - - let path = format!("avatar/{}/avatar.{}", self.uuid, image_type); - - bunny_cdn.storage.upload(path.clone(), avatar.into()).await?; - - let avatar_url = cdn_url.join(&path)?; - - use users::dsl; - update(users::table) - .filter(dsl::uuid.eq(self.uuid)) - .set(dsl::avatar.eq(avatar_url.as_str())) - .execute(conn) - .await?; - - self.avatar = Some(avatar_url.to_string()); - - Ok(()) - } } #[derive(Deserialize)] diff --git a/src/utils.rs b/src/utils.rs index 4e9d435..b7ddcc1 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,8 +1,7 @@ use actix_web::{ - cookie::{time::Duration, Cookie, SameSite}, - http::header::HeaderMap, web::BytesMut, + cookie::{Cookie, SameSite, time::Duration}, + http::header::HeaderMap, }; -use bindet::FileType; use getrandom::fill; use hex::encode; use redis::RedisError; @@ -60,22 +59,6 @@ pub fn generate_refresh_token() -> Result { Ok(encode(buf)) } -pub fn image_check(icon: BytesMut) -> Result { - let buf = std::io::Cursor::new(icon); - - let detect = bindet::detect(buf).map_err(|e| e.kind()); - - if let Ok(Some(file_type)) = detect { - if file_type.likely_to_be == vec![FileType::Jpg] { - return Ok(String::from("jpg")) - } else if file_type.likely_to_be == vec![FileType::Png] { - return Ok(String::from("png")) - } - } - - Err(Error::BadRequest("Uploaded file is not an image".to_string())) -} - impl Data { pub async fn set_cache_key( &self,