diff --git a/Cargo.toml b/Cargo.toml index e1f7a85..568466d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,7 @@ 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 2cefa4f..9647002 100644 --- a/src/api/v1/users/me.rs +++ b/src/api/v1/users/me.rs @@ -1,4 +1,5 @@ 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}; @@ -18,7 +19,7 @@ pub async fn res(req: HttpRequest, data: web::Data) -> Result, display_name: Option, @@ -26,8 +27,15 @@ struct NewInfo { email: Option, } +#[derive(Debug, MultipartForm)] +struct UploadForm { + #[multipart(limit = "100MB")] + avatar: Option, + json: Option>, +} + #[patch("/me")] -pub async fn update(req: HttpRequest, new_info: web::Json, data: web::Data) -> Result { +pub async fn update(req: HttpRequest, MultipartForm(form): MultipartForm, data: web::Data) -> Result { let headers = req.headers(); let auth_header = get_auth_header(headers)?; @@ -36,22 +44,32 @@ pub async fn update(req: HttpRequest, new_info: web::Json, data: web::D let uuid = check_access_token(auth_header, &mut conn).await?; - let me = Me::get(&mut conn, uuid).await?; + let mut me = Me::get(&mut conn, uuid).await?; - if let Some(username) = &new_info.username { - todo!(); + if let Some(avatar) = form.avatar { + let bytes = tokio::fs::read(avatar.file).await?; + + let byte_slice: &[u8] = &bytes; + + me.set_avatar(&data.bunny_cdn, &mut conn, data.config.bunny.cdn_url.clone(), byte_slice.into()).await?; } - if let Some(display_name) = &new_info.display_name { - todo!(); - } + if let Some(new_info) = form.json { + if let Some(username) = &new_info.username { + todo!(); + } - if let Some(password) = &new_info.password { - todo!(); - } + if let Some(display_name) = &new_info.display_name { + todo!(); + } - if let Some(email) = &new_info.email { - todo!(); + if let Some(password) = &new_info.password { + todo!(); + } + + if let Some(email) = &new_info.email { + todo!(); + } } Ok(HttpResponse::Ok().finish()) diff --git a/src/api/v1/users/mod.rs b/src/api/v1/users/mod.rs index 5259ed9..57f5f7d 100644 --- a/src/api/v1/users/mod.rs +++ b/src/api/v1/users/mod.rs @@ -9,6 +9,7 @@ pub fn web() -> Scope { web::scope("/users") .service(res) .service(me::res) + .service(me::update) .service(uuid::res) } diff --git a/src/structs.rs b/src/structs.rs index b4403ed..e593996 100644 --- a/src/structs.rs +++ b/src/structs.rs @@ -667,6 +667,38 @@ 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)]