diff --git a/src/database/entities/autumn.rs b/src/database/entities/autumn.rs index 10eb7c10a4bb47f16a7ce9a135183f6f9aca2034..8c57250bfdadd26fc5aa33fa6ee44560319bd277 100644 --- a/src/database/entities/autumn.rs +++ b/src/database/entities/autumn.rs @@ -1,5 +1,9 @@ +use mongodb::bson::{doc, from_document}; use serde::{Deserialize, Serialize}; +use crate::util::result::{Error, Result}; +use crate::database::*; + #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(tag = "type")] enum Metadata { @@ -32,3 +36,54 @@ pub struct File { #[serde(skip_serializing_if = "Option::is_none")] object_id: Option<String>, } + +impl File { + pub async fn find_and_use(attachment_id: &str, tag: &str, parent_type: &str, parent_id: &str) -> Result<File> { + let attachments = get_collection("attachments"); + let key = format!("{}_id", parent_type); + if let Some(doc) = attachments + .find_one( + doc! { + "_id": attachment_id, + "tag": &tag, + key.clone(): { + "$exists": false + } + }, + None, + ) + .await + .map_err(|_| Error::DatabaseError { + operation: "find_one", + with: "attachment", + })? + { + let attachment = from_document::<File>(doc).map_err(|_| Error::DatabaseError { + operation: "from_document", + with: "attachment", + })?; + + attachments + .update_one( + doc! { + "_id": &attachment.id + }, + doc! { + "$set": { + key: &parent_id + } + }, + None, + ) + .await + .map_err(|_| Error::DatabaseError { + operation: "update_one", + with: "attachment", + })?; + + Ok(attachment) + } else { + Err(Error::UnknownAttachment) + } + } +} diff --git a/src/database/entities/user.rs b/src/database/entities/user.rs index 165e9a163314ab718334285a853dbf42aa5085d5..04220db9c75acca81aa15a11ee57cd912eb6d7a5 100644 --- a/src/database/entities/user.rs +++ b/src/database/entities/user.rs @@ -63,6 +63,8 @@ pub struct User { pub id: String, pub username: String, #[serde(skip_serializing_if = "Option::is_none")] + pub avatar: Option<File>, + #[serde(skip_serializing_if = "Option::is_none")] pub relations: Option<Vec<Relationship>>, #[serde(skip_serializing_if = "Option::is_none")] diff --git a/src/routes/channels/message_send.rs b/src/routes/channels/message_send.rs index 742f47643429749a042eed5890194ddeb8437bf3..2984e02d72c2b0372da1d80ae672ca575d3f3ac2 100644 --- a/src/routes/channels/message_send.rs +++ b/src/routes/channels/message_send.rs @@ -2,7 +2,7 @@ use crate::database::*; use crate::util::result::{Error, Result}; use mongodb::{ - bson::{doc, from_document}, + bson::{doc}, options::FindOneOptions, }; use rocket_contrib::json::{Json, JsonValue}; @@ -61,52 +61,8 @@ pub async fn req(user: User, target: Ref, message: Json<Data>) -> Result<JsonVal } let id = Ulid::new().to_string(); - let attachments = get_collection("attachments"); let attachment = if let Some(attachment_id) = &message.attachment { - if let Some(doc) = attachments - .find_one( - doc! { - "_id": attachment_id, - "tag": "attachments", - "message_id": { - "$exists": false - } - }, - None, - ) - .await - .map_err(|_| Error::DatabaseError { - operation: "find_one", - with: "attachment", - })? - { - let attachment = from_document::<File>(doc).map_err(|_| Error::DatabaseError { - operation: "from_document", - with: "attachment", - })?; - - attachments - .update_one( - doc! { - "_id": &attachment.id - }, - doc! { - "$set": { - "message_id": &id - } - }, - None, - ) - .await - .map_err(|_| Error::DatabaseError { - operation: "update_one", - with: "attachment", - })?; - - Some(attachment) - } else { - return Err(Error::UnknownAttachment); - } + Some(File::find_and_use(attachment_id, "attachments", "message", &id).await?) } else { None }; diff --git a/src/routes/users/edit_user.rs b/src/routes/users/edit_user.rs index ab5e1f6e71c8f1c40eedecde2a521f013c1c6df4..5dfde797ed2d5135c633dbc84d5352430ce5346d 100644 --- a/src/routes/users/edit_user.rs +++ b/src/routes/users/edit_user.rs @@ -15,17 +15,35 @@ pub struct Data { #[serde(skip_serializing_if = "Option::is_none")] #[validate] profile: Option<UserProfile>, + #[serde(skip_serializing_if = "Option::is_none")] + #[validate(length(min = 1, max = 128))] + avatar: Option<String>, } #[patch("/<_ignore_id>", data = "<data>")] -pub async fn req(user: User, data: Json<Data>, _ignore_id: String) -> Result<()> { +pub async fn req(user: User, mut data: Json<Data>, _ignore_id: String) -> Result<()> { + if data.0.status.is_none() && data.0.profile.is_none() && data.0.avatar.is_none() { + return Ok(()) + } + data.validate() .map_err(|error| Error::FailedValidation { error })?; + let mut set = to_document(&data.0).map_err(|_| Error::DatabaseError { operation: "to_document", with: "data" })?; + + let avatar = std::mem::replace(&mut data.0.avatar, None); + let attachment = if let Some(attachment_id) = avatar { + let attachment = File::find_and_use(&attachment_id, "avatars", "user", &user.id).await?; + set.insert("avatar", to_document(&attachment).map_err(|_| Error::DatabaseError { operation: "to_document", with: "attachment" })?); + Some(attachment) + } else { + None + }; + get_collection("users") .update_one( doc! { "_id": &user.id }, - doc! { "$set": to_document(&data.0).map_err(|_| Error::DatabaseError { operation: "to_document", with: "data" })? }, + doc! { "$set": set }, None ) .await @@ -41,5 +59,15 @@ pub async fn req(user: User, data: Json<Data>, _ignore_id: String) -> Result<()> .ok(); } + if let Some(avatar) = attachment { + ClientboundNotification::UserUpdate { + id: user.id.clone(), + data: json!({ "avatar": avatar }), + } + .publish(user.id.clone()) + .await + .ok(); + } + Ok(()) } diff --git a/src/routes/users/get_avatar.rs b/src/routes/users/get_avatar.rs deleted file mode 100644 index d928a4474749d3eaeed89a3c5f6fa837975a0ce7..0000000000000000000000000000000000000000 --- a/src/routes/users/get_avatar.rs +++ /dev/null @@ -1,49 +0,0 @@ -use md5; -use mongodb::bson::doc; -use mongodb::options::FindOneOptions; -use rocket::response::Redirect; -use urlencoding; - -use crate::database::*; -use crate::util::result::{Error, Result}; -use crate::util::variables::PUBLIC_URL; - -#[get("/<target>/avatar")] -pub async fn req(target: Ref) -> Result<Redirect> { - let doc = get_collection("accounts") - .find_one( - doc! { - "_id": &target.id - }, - FindOneOptions::builder() - .projection(doc! { "email": 1 }) - .build(), - ) - .await - .map_err(|_| Error::DatabaseError { - operation: "find_one", - with: "user", - })? - .ok_or_else(|| Error::UnknownUser)?; - - let email = doc - .get_str("email") - .map_err(|_| Error::DatabaseError { - operation: "get_str(email)", - with: "user", - })? - .to_lowercase(); - - let url = format!( - "https://www.gravatar.com/avatar/{:x}?s=128&d={}", - md5::compute(email), - urlencoding::encode(&format!( - "{}/users/{}/default_avatar", - *PUBLIC_URL, &target.id - )) - ); - - dbg!(&url); - - Ok(Redirect::to(url)) -} diff --git a/src/routes/users/mod.rs b/src/routes/users/mod.rs index 331186db25f28a91db24d72ffbc5b128a5ec4631..c9805545e3e2218c37c4c93b98c53627a85c0880 100644 --- a/src/routes/users/mod.rs +++ b/src/routes/users/mod.rs @@ -10,7 +10,6 @@ mod fetch_relationship; mod fetch_relationships; mod fetch_user; mod find_mutual; -mod get_avatar; mod get_default_avatar; mod open_dm; mod remove_friend; @@ -23,7 +22,6 @@ pub fn routes() -> Vec<Route> { edit_user::req, change_username::req, get_default_avatar::req, - get_avatar::req, fetch_profile::req, // Direct Messaging fetch_dms::req,