use super::Response; use crate::database::{self, get_relationship, get_relationship_internal, mutual, Relationship}; use crate::guards::auth::UserRef; use crate::notifications::{ self, events::{users::*, Notification}, }; use crate::routes::channel; use bson::doc; use mongodb::options::FindOptions; use rocket_contrib::json::Json; use serde::{Deserialize, Serialize}; use ulid::Ulid; /// retrieve your user information #[get("/@me")] pub fn me(user: UserRef) -> Response { if let Some(info) = user.fetch_data(doc! { "email": 1 }) { Response::Success(json!({ "id": user.id, "username": user.username, "email": info.get_str("email").unwrap(), "verified": user.email_verified, })) } else { Response::InternalServerError( json!({ "error": "Failed to fetch information from database." }), ) } } /// retrieve another user's information #[get("/<target>")] pub fn user(user: UserRef, target: UserRef) -> Response { Response::Success(json!({ "id": target.id, "username": target.username, "relationship": get_relationship(&user, &target) as i32, "mutual": { "guilds": mutual::find_mutual_guilds(&user.id, &target.id), "friends": mutual::find_mutual_friends(&user.id, &target.id), "groups": mutual::find_mutual_groups(&user.id, &target.id), } })) } #[derive(Serialize, Deserialize)] pub struct LookupQuery { username: String, } /// lookup a user on Revolt /// currently only supports exact username searches #[post("/lookup", data = "<query>")] pub fn lookup(user: UserRef, query: Json<LookupQuery>) -> Response { let relationships = user.fetch_relationships(); let col = database::get_collection("users"); if let Ok(users) = col.find( doc! { "username": query.username.clone() }, FindOptions::builder() .projection(doc! { "_id": 1, "username": 1 }) .limit(10) .build(), ) { let mut results = Vec::new(); for item in users { if let Ok(doc) = item { let id = doc.get_str("id").unwrap(); results.push(json!({ "id": id, "username": doc.get_str("username").unwrap(), "relationship": get_relationship_internal(&user.id, &id, &relationships) as i32 })); } } Response::Success(json!(results)) } else { Response::InternalServerError(json!({ "error": "Failed database query." })) } } /// retrieve all of your DMs #[get("/@me/dms")] pub fn dms(user: UserRef) -> Response { let col = database::get_collection("channels"); if let Ok(results) = col.find( doc! { "$or": [ { "type": channel::ChannelType::DM as i32 }, { "type": channel::ChannelType::GROUPDM as i32 } ], "recipients": user.id }, FindOptions::builder().projection(doc! {}).build(), ) { let mut channels = Vec::new(); for item in results { if let Ok(doc) = item { let id = doc.get_str("_id").unwrap(); let recipients = doc.get_array("recipients").unwrap(); match doc.get_i32("type").unwrap() { 0 => { channels.push(json!({ "id": id, "type": 0, "recipients": recipients, })); } 1 => { channels.push(json!({ "id": id, "type": 1, "recipients": recipients, "name": doc.get_str("name").unwrap(), "owner": doc.get_str("owner").unwrap(), "description": doc.get_str("description").unwrap_or(""), })); } _ => unreachable!(), } } } Response::Success(json!(channels)) } else { Response::InternalServerError(json!({ "error": "Failed database query." })) } } /// open a DM with a user #[get("/<target>/dm")] pub fn dm(user: UserRef, target: UserRef) -> Response { let col = database::get_collection("channels"); if let Ok(result) = col.find_one( doc! { "type": channel::ChannelType::DM as i32, "recipients": { "$all": [ user.id.clone(), target.id.clone() ] } }, None ) { if let Some(channel) = result { Response::Success( json!({ "id": channel.get_str("_id").unwrap() })) } else { let id = Ulid::new(); if col.insert_one( doc! { "_id": id.to_string(), "type": channel::ChannelType::DM as i32, "recipients": [ user.id, target.id ], "active": false }, None ).is_ok() { Response::Success(json!({ "id": id.to_string() })) } else { Response::InternalServerError(json!({ "error": "Failed to create new channel." })) } } } else { Response::InternalServerError(json!({ "error": "Failed server query." })) } } /// retrieve all of your friends #[get("/@me/friend")] pub fn get_friends(user: UserRef) -> Response { let relationships = user.fetch_relationships(); let mut results = Vec::new(); if let Some(arr) = relationships { for item in arr { results.push(json!({ "id": item.id, "status": item.status })) } } Response::Success(json!(results)) } /// retrieve friend status with user #[get("/<target>/friend")] pub fn get_friend(user: UserRef, target: UserRef) -> Response { Response::Success(json!({ "status": get_relationship(&user, &target) as i32 })) } /// create or accept a friend request #[put("/<target>/friend")] pub fn add_friend(user: UserRef, target: UserRef) -> Response { let col = database::get_collection("users"); match get_relationship(&user, &target) { Relationship::Friend => Response::BadRequest(json!({ "error": "Already friends." })), Relationship::Outgoing => { Response::BadRequest(json!({ "error": "Already sent a friend request." })) } Relationship::Incoming => { if col .update_one( doc! { "_id": user.id.clone(), "relations.id": target.id.clone() }, doc! { "$set": { "relations.$.status": Relationship::Friend as i32 } }, None, ) .is_ok() { if col .update_one( doc! { "_id": target.id.clone(), "relations.id": user.id.clone() }, doc! { "$set": { "relations.$.status": Relationship::Friend as i32 } }, None, ) .is_ok() { notifications::send_message_threaded( vec![target.id.clone()], None, Notification::user_friend_status(FriendStatus { id: user.id.clone(), status: Relationship::Friend as i32, }), ); notifications::send_message_threaded( vec![user.id.clone()], None, Notification::user_friend_status(FriendStatus { id: target.id.clone(), status: Relationship::Friend as i32, }), ); Response::Success(json!({ "status": Relationship::Friend as i32 })) } else { Response::InternalServerError( json!({ "error": "Failed to commit! Try re-adding them as a friend." }), ) } } else { Response::InternalServerError( json!({ "error": "Failed to commit to database, try again." }), ) } } Relationship::Blocked => { Response::BadRequest(json!({ "error": "You have blocked this person." })) } Relationship::BlockedOther => { Response::Conflict(json!({ "error": "You have been blocked by this person." })) } Relationship::NONE => { if col .update_one( doc! { "_id": user.id.clone() }, doc! { "$push": { "relations": { "id": target.id.clone(), "status": Relationship::Outgoing as i32 } } }, None, ) .is_ok() { if col .update_one( doc! { "_id": target.id.clone() }, doc! { "$push": { "relations": { "id": user.id.clone(), "status": Relationship::Incoming as i32 } } }, None, ) .is_ok() { notifications::send_message_threaded( vec![user.id.clone()], None, Notification::user_friend_status(FriendStatus { id: target.id.clone(), status: Relationship::Outgoing as i32, }), ); notifications::send_message_threaded( vec![target.id.clone()], None, Notification::user_friend_status(FriendStatus { id: user.id.clone(), status: Relationship::Incoming as i32, }), ); Response::Success(json!({ "status": Relationship::Outgoing as i32 })) } else { Response::InternalServerError( json!({ "error": "Failed to commit! Try re-adding them as a friend." }), ) } } else { Response::InternalServerError( json!({ "error": "Failed to commit to database, try again." }), ) } } Relationship::SELF => { Response::BadRequest(json!({ "error": "You're already friends with yourself, no? c:" })) } } } /// remove a friend or deny a request #[delete("/<target>/friend")] pub fn remove_friend(user: UserRef, target: UserRef) -> Response { let col = database::get_collection("users"); match get_relationship(&user, &target) { Relationship::Friend | Relationship::Outgoing | Relationship::Incoming => { if col .update_one( doc! { "_id": user.id.clone() }, doc! { "$pull": { "relations": { "id": target.id.clone() } } }, None, ) .is_ok() { if col .update_one( doc! { "_id": target.id.clone() }, doc! { "$pull": { "relations": { "id": user.id.clone() } } }, None, ) .is_ok() { notifications::send_message_threaded( vec![user.id.clone()], None, Notification::user_friend_status(FriendStatus { id: target.id.clone(), status: Relationship::NONE as i32, }), ); notifications::send_message_threaded( vec![target.id.clone()], None, Notification::user_friend_status(FriendStatus { id: user.id.clone(), status: Relationship::NONE as i32, }), ); Response::Success(json!({ "status": Relationship::NONE as i32 })) } else { Response::InternalServerError( json!({ "error": "Failed to commit! Target remains in same state." }), ) } } else { Response::InternalServerError( json!({ "error": "Failed to commit to database, try again." }), ) } } Relationship::Blocked | Relationship::BlockedOther | Relationship::NONE | Relationship::SELF => Response::BadRequest(json!({ "error": "This has no effect." })), } } /// block a user #[put("/<target>/block")] pub fn block_user(user: UserRef, target: UserRef) -> Response { let col = database::get_collection("users"); match get_relationship(&user, &target) { Relationship::Friend | Relationship::Incoming | Relationship::Outgoing => { if col .update_one( doc! { "_id": user.id.clone(), "relations.id": target.id.clone() }, doc! { "$set": { "relations.$.status": Relationship::Blocked as i32 } }, None, ) .is_ok() { if col .update_one( doc! { "_id": target.id.clone(), "relations.id": user.id.clone() }, doc! { "$set": { "relations.$.status": Relationship::BlockedOther as i32 } }, None, ) .is_ok() { notifications::send_message_threaded( vec![user.id.clone()], None, Notification::user_friend_status(FriendStatus { id: target.id.clone(), status: Relationship::Blocked as i32, }), ); notifications::send_message_threaded( vec![target.id.clone()], None, Notification::user_friend_status(FriendStatus { id: user.id.clone(), status: Relationship::BlockedOther as i32, }), ); Response::Success(json!({ "status": Relationship::Blocked as i32 })) } else { Response::InternalServerError( json!({ "error": "Failed to commit! Try blocking the user again, remove it first." }), ) } } else { Response::InternalServerError( json!({ "error": "Failed to commit to database, try again." }), ) } } Relationship::NONE => { if col .update_one( doc! { "_id": user.id.clone(), }, doc! { "$push": { "relations": { "id": target.id.clone(), "status": Relationship::Blocked as i32, } } }, None, ) .is_ok() { if col .update_one( doc! { "_id": target.id.clone(), }, doc! { "$push": { "relations": { "id": user.id.clone(), "status": Relationship::BlockedOther as i32, } } }, None, ) .is_ok() { notifications::send_message_threaded( vec![user.id.clone()], None, Notification::user_friend_status(FriendStatus { id: target.id.clone(), status: Relationship::Blocked as i32, }), ); notifications::send_message_threaded( vec![target.id.clone()], None, Notification::user_friend_status(FriendStatus { id: user.id.clone(), status: Relationship::BlockedOther as i32, }), ); Response::Success(json!({ "status": Relationship::Blocked as i32 })) } else { Response::InternalServerError( json!({ "error": "Failed to commit! Try blocking the user again, remove it first." }), ) } } else { Response::InternalServerError( json!({ "error": "Failed to commit to database, try again." }), ) } } Relationship::Blocked => { Response::BadRequest(json!({ "error": "Already blocked this person." })) } Relationship::BlockedOther => { if col .update_one( doc! { "_id": user.id.clone(), "relations.id": target.id.clone() }, doc! { "$set": { "relations.$.status": Relationship::Blocked as i32 } }, None, ) .is_ok() { notifications::send_message_threaded( vec![user.id.clone()], None, Notification::user_friend_status(FriendStatus { id: target.id.clone(), status: Relationship::Blocked as i32, }), ); Response::Success(json!({ "status": Relationship::Blocked as i32 })) } else { Response::InternalServerError( json!({ "error": "Failed to commit to database, try again." }), ) } } Relationship::SELF => Response::BadRequest(json!({ "error": "This has no effect." })), } } /// unblock a user #[delete("/<target>/block")] pub fn unblock_user(user: UserRef, target: UserRef) -> Response { let col = database::get_collection("users"); match get_relationship(&user, &target) { Relationship::Blocked => match get_relationship(&target, &user) { Relationship::Blocked => { if col .update_one( doc! { "_id": user.id.clone(), "relations.id": target.id.clone() }, doc! { "$set": { "relations.$.status": Relationship::BlockedOther as i32 } }, None, ) .is_ok() { notifications::send_message_threaded( vec![user.id.clone()], None, Notification::user_friend_status(FriendStatus { id: target.id.clone(), status: Relationship::BlockedOther as i32, }), ); Response::Success(json!({ "status": Relationship::BlockedOther as i32 })) } else { Response::InternalServerError( json!({ "error": "Failed to commit to database, try again." }), ) } } Relationship::BlockedOther => { if col .update_one( doc! { "_id": user.id.clone() }, doc! { "$pull": { "relations": { "id": target.id.clone() } } }, None, ) .is_ok() { if col .update_one( doc! { "_id": target.id.clone() }, doc! { "$pull": { "relations": { "id": user.id.clone() } } }, None, ) .is_ok() { notifications::send_message_threaded( vec![user.id.clone()], None, Notification::user_friend_status(FriendStatus { id: target.id.clone(), status: Relationship::NONE as i32, }), ); notifications::send_message_threaded( vec![target.id.clone()], None, Notification::user_friend_status(FriendStatus { id: user.id.clone(), status: Relationship::NONE as i32, }), ); Response::Success(json!({ "status": Relationship::NONE as i32 })) } else { Response::InternalServerError( json!({ "error": "Failed to commit! Target remains in same state." }), ) } } else { Response::InternalServerError( json!({ "error": "Failed to commit to database, try again." }), ) } } _ => unreachable!(), }, Relationship::BlockedOther => { Response::BadRequest(json!({ "error": "Cannot remove block by other user." })) } Relationship::Friend | Relationship::Incoming | Relationship::Outgoing | Relationship::SELF | Relationship::NONE => Response::BadRequest(json!({ "error": "This has no effect." })), } } #[options("/<_target>")] pub fn user_preflight(_target: String) -> Response { Response::Result(super::Status::Ok) } #[options("/lookup")] pub fn lookup_preflight() -> Response { Response::Result(super::Status::Ok) } #[options("/@me/dms")] pub fn dms_preflight() -> Response { Response::Result(super::Status::Ok) } #[options("/<_target>/dm")] pub fn dm_preflight(_target: String) -> Response { Response::Result(super::Status::Ok) } #[options("/<_target>/friend")] pub fn friend_preflight(_target: String) -> Response { Response::Result(super::Status::Ok) } #[options("/<_target>/block")] pub fn block_user_preflight(_target: String) -> Response { Response::Result(super::Status::Ok) }