From 20c82ddef30b1384e8adb5cf46c3a6d991da4fe3 Mon Sep 17 00:00:00 2001
From: Paul Makles <paulmakles@gmail.com>
Date: Sun, 26 Jan 2020 14:16:58 +0000
Subject: [PATCH] Add all friend routes.

---
 src/auth.rs           |  25 ++++-
 src/database.rs       |   6 +-
 src/routes/account.rs |  10 +-
 src/routes/mod.rs     |   2 +-
 src/routes/user.rs    | 244 +++++++++++++++++++++++++++++++++++++++---
 5 files changed, 262 insertions(+), 25 deletions(-)

diff --git a/src/auth.rs b/src/auth.rs
index 349311b..2e5e9ef 100644
--- a/src/auth.rs
+++ b/src/auth.rs
@@ -1,6 +1,6 @@
 use rocket::Outcome;
-use rocket::http::Status;
-use rocket::request::{self, Request, FromRequest};
+use rocket::http::{ Status, RawStr };
+use rocket::request::{ self, Request, FromRequest, FromParam };
 
 use bson::{ bson, doc, ordered::OrderedDocument };
 use ulid::Ulid;
@@ -45,4 +45,23 @@ impl<'a, 'r> FromRequest<'a, 'r> for User {
             _ => Outcome::Failure((Status::BadRequest, AuthError::BadCount)),
         }
     }
-}
\ No newline at end of file
+}
+
+impl<'r> FromParam<'r> for User {
+    type Error = &'r RawStr;
+
+    fn from_param(param: &'r RawStr) -> Result<Self, Self::Error> {
+		let col = database::get_db().collection("users");
+		let result = col.find_one(doc! { "_id": param.to_string() }, None).unwrap();
+
+		if let Some(user) = result {
+			Ok(User(
+				Ulid::from_string(user.get_str("_id").unwrap()).unwrap(),
+				user.get_str("username").unwrap().to_string(),
+				user
+			))
+		} else {
+			Err(param)
+		}
+    }
+}
diff --git a/src/database.rs b/src/database.rs
index ca71b45..551688e 100644
--- a/src/database.rs
+++ b/src/database.rs
@@ -1,4 +1,4 @@
-use mongodb::{ Client, Database };
+use mongodb::{ Client, Collection, Database };
 use std::env;
 
 use once_cell::sync::OnceCell;
@@ -19,3 +19,7 @@ pub fn get_connection() -> &'static Client {
 pub fn get_db() -> Database {
 	get_connection().database("revolt")
 }
+
+pub fn get_collection(collection: &str) -> Collection {
+	get_db().collection(collection)
+}
diff --git a/src/routes/account.rs b/src/routes/account.rs
index 17e556c..7e0aedd 100644
--- a/src/routes/account.rs
+++ b/src/routes/account.rs
@@ -33,7 +33,7 @@ pub struct Create {
 /// (3) add user and send email verification
 #[post("/create", data = "<info>")]
 pub fn create(info: Json<Create>) -> JsonValue {
-	let col = database::get_db().collection("users");
+	let col = database::get_collection("users");
 
 	if info.username.len() < 2 || info.username.len() > 32 {
 		return json!({
@@ -108,7 +108,7 @@ pub fn create(info: Json<Create>) -> JsonValue {
 /// (3) set account as verified
 #[get("/verify/<code>")]
 pub fn verify_email(code: String) -> JsonValue {
-	let col = database::get_db().collection("users");
+	let col = database::get_collection("users");
 
 	if let Some(u) =
 		col.find_one(doc! { "email_verification.code": code.clone() }, None).expect("Failed user lookup") {
@@ -166,8 +166,8 @@ pub struct Resend {
 /// (2) check for rate limit
 /// (3) resend the email
 #[post("/resend", data = "<info>")]
-pub fn resend_email(info: Json<Resend>) -> JsonValue { 
-	let col = database::get_db().collection("users");
+pub fn resend_email(info: Json<Resend>) -> JsonValue {
+	let col = database::get_collection("users");
 
 	if let Some(u) =
 		col.find_one(doc! { "email_verification.target": info.email.clone() }, None).expect("Failed user lookup") {
@@ -239,7 +239,7 @@ pub struct Login {
 /// (3) return access token
 #[post("/login", data = "<info>")]
 pub fn login(info: Json<Login>) -> JsonValue {
-	let col = database::get_db().collection("users");
+	let col = database::get_collection("users");
 
 	if let Some(u) =
 		col.find_one(doc! { "email": info.email.clone() }, None).expect("Failed user lookup") {
diff --git a/src/routes/mod.rs b/src/routes/mod.rs
index 3640104..a32647e 100644
--- a/src/routes/mod.rs
+++ b/src/routes/mod.rs
@@ -6,5 +6,5 @@ mod user;
 pub fn mount(rocket: Rocket) -> Rocket {
 	rocket
 		.mount("/api/account", routes![ account::create, account::verify_email, account::resend_email, account::login ])
-		.mount("/api/users", routes![ user::me, user::dms, user::lookup ])
+		.mount("/api/users", routes![ user::me, user::user, user::lookup, user::dms, user::dm, user::get_friends, user::get_friend, user::add_friend, user::remove_friend ])
 }
diff --git a/src/routes/user.rs b/src/routes/user.rs
index 8f74db4..74cd5bc 100644
--- a/src/routes/user.rs
+++ b/src/routes/user.rs
@@ -22,8 +22,8 @@ pub fn me(user: User) -> JsonValue {
 }
 
 /// retrieve another user's information
-#[get("/<id>")]
-pub fn user(user: User, id: String) -> JsonValue {
+#[get("/<target>")]
+pub fn user(user: User, target: User) -> JsonValue {
 	json!([])
 }
 
@@ -36,7 +36,7 @@ pub struct Query {
 /// currently only supports exact username searches
 #[post("/lookup", data = "<query>")]
 pub fn lookup(_user: User, query: Json<Query>) -> JsonValue {
-	let col = database::get_db().collection("users");
+	let col = database::get_collection("users");
 
 	let users = col.find(
 		doc! { "username": query.username.clone() },
@@ -64,31 +64,245 @@ pub fn dms(user: User) -> JsonValue {
 }
 
 /// open a DM with a user
-#[get("/<id>/dm")]
-pub fn dm(user: User, id: String) -> JsonValue {
+#[get("/<target>/dm")]
+pub fn dm(user: User, target: User) -> JsonValue {
 	json!([])
 }
 
+enum Relationship {
+	FRIEND = 0,
+	OUTGOING = 1,
+	INCOMING = 2,
+	BLOCKED = 3,
+	BLOCKED_OTHER = 4,
+	NONE = 5,
+	SELF = 6,
+}
+
+fn get_relationship(a: &User, b: &User) -> Relationship {
+	if a.0.to_string() == b.0.to_string() {
+		return Relationship::SELF
+	}
+
+	if let Ok(arr) = b.2.get_array("relations") {
+		let id = a.0.to_string();
+		
+		for entry in arr {
+			let relation = entry.as_document().expect("Expected document in relations array.");
+
+			if relation.get_str("id").expect("DB[id]") == id {
+
+				match relation.get_i32("status").expect("DB[status]") {
+					0 => {
+						return Relationship::FRIEND
+					},
+					1 => {
+						return Relationship::INCOMING
+					},
+					2 => {
+						return Relationship::OUTGOING
+					},
+					3 => {
+						return Relationship::BLOCKED_OTHER
+					}
+					_ => {
+						return Relationship::NONE
+					}
+				}
+
+			}
+		}
+	}
+
+	Relationship::NONE
+}
+
 /// retrieve all of your friends
 #[get("/@me/friend")]
 pub fn get_friends(user: User) -> JsonValue {
-	json!([])
+	let mut results = Vec::new();
+	if let Ok(arr) = user.2.get_array("relations") {
+		for item in arr {
+			let doc = item.as_document().expect("Expected document in relations array.");
+			results.push(
+				json!({
+					"id": doc.get_str("id").expect("DB[id]"),
+					"status": doc.get_i32("status").expect("DB[status]")
+				})
+			)
+		}
+	}
+	
+	json!(results)
 }
 
 /// retrieve friend status with user
-#[get("/<id>/friend")]
-pub fn get_friend(user: User, id: String) -> JsonValue {
-	json!([])
+#[get("/<target>/friend")]
+pub fn get_friend(user: User, target: User) -> JsonValue {
+	let relationship = get_relationship(&user, &target);
+
+	json!({
+		"id": target.0.to_string(),
+		"status": relationship as u8
+	})
 }
 
 /// create or accept a friend request
-#[put("/<id>/friend")]
-pub fn add_friend(user: User, id: String) -> JsonValue {
-	json!([])
+#[put("/<target>/friend")]
+pub fn add_friend(user: User, target: User) -> JsonValue {
+	let col = database::get_collection("users");
+
+	let relationship = get_relationship(&user, &target);
+	let User ( id, _, _ ) = user;
+	let User ( tid, _, _ ) = target;
+
+	match relationship {
+		Relationship::FRIEND =>
+			json!({
+				"success": false,
+				"error": "Already friends."
+			}),
+		Relationship::OUTGOING =>
+			json!({
+				"success": false,
+				"error": "Already sent a friend request."
+			}),
+		Relationship::INCOMING => {
+			col.update_one(
+				doc! {
+					"_id": id.to_string(),
+					"relations.id": tid.to_string()
+				},
+				doc! {
+					"$set": {
+						"relations.$.status": Relationship::FRIEND as i32
+					}
+				},
+				None
+			).expect("Failed update query.");
+			
+			col.update_one(
+				doc! {
+					"_id": tid.to_string(),
+					"relations.id": id.to_string()
+				},
+				doc! {
+					"$set": {
+						"relations.$.status": Relationship::FRIEND as i32
+					}
+				},
+				None
+			).expect("Failed update query.");
+
+			json!({
+				"success": true
+			})
+		},
+		Relationship::BLOCKED =>
+			json!({
+				"success": false,
+				"error": "You have blocked this person."
+			}),
+		Relationship::BLOCKED_OTHER =>
+			json!({
+				"success": false,
+				"error": "You have been blocked by this person."
+			}),
+		Relationship::NONE => {
+			col.update_one(
+				doc! {
+					"_id": id.to_string()
+				},
+				doc! {
+					"$push": {
+						"relations": {
+							"id": tid.to_string(),
+							"status": Relationship::OUTGOING as i32
+						}
+					}
+				},
+				None
+			).expect("Failed update query.");
+			
+			col.update_one(
+				doc! {
+					"_id": tid.to_string()
+				},
+				doc! {
+					"$push": {
+						"relations": {
+							"id": id.to_string(),
+							"status": Relationship::INCOMING as i32
+						}
+					}
+				},
+				None
+			).expect("Failed update query.");
+
+			json!({
+				"success": true
+			})
+		},
+		Relationship::SELF =>
+			json!({
+				"success": false,
+				"error": "Cannot add yourself as a friend."
+			})
+	}
 }
 
 /// remove a friend or deny a request
-#[delete("/<id>/friend")]
-pub fn remove_friend(user: User, id: String) -> JsonValue {
-	json!([])
+#[delete("/<target>/friend")]
+pub fn remove_friend(user: User, target: User) -> JsonValue {
+	let col = database::get_collection("users");
+
+	let relationship = get_relationship(&user, &target);
+	let User ( id, _, _ ) = user;
+	let User ( tid, _, _ ) = target;
+
+	match relationship {
+		Relationship::FRIEND |
+		Relationship::OUTGOING |
+		Relationship::INCOMING => {
+			col.update_one(
+				doc! {
+					"_id": id.to_string()
+				},
+				doc! {
+					"$pull": {
+						"relations": {
+							"id": tid.to_string()
+						}
+					}
+				},
+				None
+			).expect("Failed update query.");
+
+			col.update_one(
+				doc! {
+					"_id": tid.to_string()
+				},
+				doc! {
+					"$pull": {
+						"relations": {
+							"id": id.to_string()
+						}
+					}
+				},
+				None
+			).expect("Failed update query.");
+
+			json!({
+				"success": true
+			})
+		},
+		Relationship::BLOCKED |
+		Relationship::BLOCKED_OTHER |
+		Relationship::NONE |
+		Relationship::SELF =>
+			json!({
+				"success": false,
+				"error": "This has no effect."
+			})
+	}
 }
-- 
GitLab