Re-implement user relationships.

This commit is contained in:
Paul Makles
2020-12-29 16:32:22 +00:00
parent 84d09db9b3
commit c704f13d72
14 changed files with 436 additions and 157 deletions

View File

@@ -2,14 +2,26 @@ use mongodb::bson::{doc, from_bson, Bson};
use rauth::auth::Session;
use rocket::http::Status;
use serde::{Deserialize, Serialize};
use crate::database::guards::reference::Ref;
use crate::database::get_collection;
use rocket::request::{self, FromRequest, Outcome, Request};
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum RelationshipStatus {
None,
User,
Friend,
Outgoing,
Incoming,
Blocked,
BlockedOther
}
#[derive(Serialize, Deserialize, Debug)]
pub struct Relationship {
#[serde(rename = "_id")]
pub id: String,
pub status: u8,
pub status: RelationshipStatus,
}
#[derive(Serialize, Deserialize, Debug)]
@@ -47,3 +59,9 @@ impl<'a, 'r> FromRequest<'a, 'r> for User {
}
}
}
impl User {
pub fn as_ref(&self) -> Ref {
Ref { id: self.id.to_string() }
}
}

View File

@@ -18,11 +18,11 @@ impl Ref {
Ok(Ref { id })
}
pub async fn fetch_user(self) -> Result<User> {
pub async fn fetch_user(&self) -> Result<User> {
let doc = get_collection("users")
.find_one(
doc! {
"_id": self.id
"_id": &self.id
},
None
)

View File

@@ -30,3 +30,22 @@ pub async fn temp_calc_perm(_user: &User, _target: &User) -> UserPermissions<[u3
UserPermissions([UserPermission::Access + UserPermission::SendMessage + UserPermission::Invite])
}
use crate::database::entities::RelationshipStatus;
use crate::database::guards::reference::Ref;
pub fn get_relationship(a: &User, b: &Ref) -> RelationshipStatus {
if a.id == b.id {
return RelationshipStatus::Friend;
}
if let Some(relations) = &a.relations {
if let Some(relationship) = relations
.iter()
.find(|x| x.id == b.id) {
return relationship.status.clone();
}
}
RelationshipStatus::None
}

View File

@@ -7,10 +7,15 @@ use rocket_contrib::json::Json;
use rauth::auth::Session;
use validator::Validate;
use mongodb::bson::doc;
use regex::Regex;
lazy_static! {
static ref RE_USERNAME: Regex = Regex::new(r"^[a-zA-Z0-9-_]+$").unwrap();
}
#[derive(Validate, Serialize, Deserialize)]
pub struct Data {
#[validate(length(min = 2, max = 32))]
#[validate(length(min = 2, max = 32), regex = "RE_USERNAME")]
username: String
}

View File

@@ -0,0 +1,84 @@
use crate::{database::{entities::{User, RelationshipStatus}, get_collection, guards::reference::Ref, permissions::get_relationship}, util::result::Error};
use rocket_contrib::json::JsonValue;
use crate::util::result::Result;
use mongodb::bson::doc;
use futures::try_join;
#[put("/<target>/friend")]
pub async fn req(user: User, target: Ref) -> Result<JsonValue> {
let col = get_collection("users");
match get_relationship(&user, &target) {
RelationshipStatus::User => return Err(Error::NoEffect),
RelationshipStatus::Friend => return Err(Error::AlreadyFriends),
RelationshipStatus::Outgoing => return Err(Error::AlreadySentRequest),
RelationshipStatus::Blocked => return Err(Error::Blocked),
RelationshipStatus::BlockedOther => return Err(Error::BlockedByOther),
RelationshipStatus::Incoming => {
match try_join!(
col.update_one(
doc! {
"_id": &user.id,
"relations._id": &target.id
},
doc! {
"$set": {
"relations.$.status": "Friend"
}
},
None
),
col.update_one(
doc! {
"_id": &target.id,
"relations._id": &user.id
},
doc! {
"$set": {
"relations.$.status": "Friend"
}
},
None
)
) {
Ok(_) => Ok(json!({ "status": "Friend" })),
Err(_) => Err(Error::DatabaseError { operation: "update_one", with: "user" })
}
}
RelationshipStatus::None => {
match try_join!(
col.update_one(
doc! {
"_id": &user.id
},
doc! {
"$push": {
"relations": {
"_id": &target.id,
"status": "Outgoing"
}
}
},
None
),
col.update_one(
doc! {
"_id": &target.id
},
doc! {
"$push": {
"relations": {
"_id": &user.id,
"status": "Incoming"
}
}
},
None
)
) {
Ok(_) => Ok(json!({ "status": "Outgoing" })),
Err(_) => Err(Error::DatabaseError { operation: "update_one", with: "user" })
}
}
}
}

View File

@@ -0,0 +1,101 @@
use crate::{database::entities::RelationshipStatus, database::guards::reference::Ref, database::entities::User, database::permissions::get_relationship, util::result::Error, database::get_collection};
use rocket_contrib::json::JsonValue;
use crate::util::result::Result;
use mongodb::bson::doc;
use futures::try_join;
#[put("/<target>/block")]
pub async fn req(user: User, target: Ref) -> Result<JsonValue> {
let col = get_collection("users");
match get_relationship(&user, &target) {
RelationshipStatus::User |
RelationshipStatus::Blocked => Err(Error::NoEffect),
RelationshipStatus::BlockedOther => {
col.update_one(
doc! {
"_id": &user.id,
"relations._id": &target.id
},
doc! {
"$set": {
"relations.$.status": "Blocked"
}
},
None
)
.await
.map_err(|_| Error::DatabaseError { operation: "update_one", with: "user" })?;
Ok(json!({ "status": "Blocked" }))
},
RelationshipStatus::None => {
match try_join!(
col.update_one(
doc! {
"_id": &user.id
},
doc! {
"$push": {
"relations": {
"_id": &target.id,
"status": "Blocked"
}
}
},
None
),
col.update_one(
doc! {
"_id": &target.id
},
doc! {
"$push": {
"relations": {
"_id": &user.id,
"status": "BlockedOther"
}
}
},
None
)
) {
Ok(_) => Ok(json!({ "status": "Blocked" })),
Err(_) => Err(Error::DatabaseError { operation: "update_one", with: "user" })
}
},
RelationshipStatus::Friend |
RelationshipStatus::Incoming |
RelationshipStatus::Outgoing => {
match try_join!(
col.update_one(
doc! {
"_id": &user.id,
"relations._id": &target.id
},
doc! {
"$set": {
"relations.$.status": "Blocked"
}
},
None
),
col.update_one(
doc! {
"_id": &target.id,
"relations._id": &user.id
},
doc! {
"$set": {
"relations.$.status": "BlockedOther"
}
},
None
)
) {
Ok(_) => Ok(json!({ "status": "Blocked" })),
Err(_) => Err(Error::DatabaseError { operation: "update_one", with: "user" })
}
}
}
}

View File

@@ -0,0 +1,10 @@
use crate::database::{entities::User, guards::reference::Ref, permissions::get_relationship};
use rocket_contrib::json::JsonValue;
use crate::util::result::Result;
#[get("/<target>/relationship")]
pub async fn req(user: User, target: Ref) -> Result<JsonValue> {
Ok(json!({
"status": get_relationship(&user, &target)
}))
}

View File

@@ -0,0 +1,14 @@
use crate::database::entities::User;
use rocket_contrib::json::JsonValue;
use crate::util::result::Result;
#[get("/relationships")]
pub async fn req(user: User) -> Result<JsonValue> {
Ok(
if let Some(vec) = user.relations {
json!(vec)
} else {
json!([])
}
)
}

View File

@@ -3,11 +3,28 @@ use rocket::Route;
mod fetch_user;
mod fetch_dms;
mod open_dm;
mod fetch_relationships;
mod fetch_relationship;
mod add_friend;
mod remove_friend;
mod block_user;
mod unblock_user;
pub fn routes() -> Vec<Route> {
routes! [
// User Information
fetch_user::req,
// Direct Messaging
fetch_dms::req,
open_dm::req,
// Relationships
fetch_relationships::req,
fetch_relationship::req,
add_friend::req,
remove_friend::req,
block_user::req,
unblock_user::req,
]
}

View File

@@ -0,0 +1,52 @@
use crate::{database::entities::RelationshipStatus, database::guards::reference::Ref, database::entities::User, database::permissions::get_relationship, util::result::Error, database::get_collection};
use rocket_contrib::json::JsonValue;
use crate::util::result::Result;
use mongodb::bson::doc;
use futures::try_join;
#[delete("/<target>/friend")]
pub async fn req(user: User, target: Ref) -> Result<JsonValue> {
let col = get_collection("users");
match get_relationship(&user, &target) {
RelationshipStatus::Blocked |
RelationshipStatus::BlockedOther |
RelationshipStatus::User |
RelationshipStatus::None => Err(Error::NoEffect),
RelationshipStatus::Friend |
RelationshipStatus::Outgoing |
RelationshipStatus::Incoming => {
match try_join!(
col.update_one(
doc! {
"_id": &user.id
},
doc! {
"$pull": {
"relations": {
"_id": &target.id
}
}
},
None
),
col.update_one(
doc! {
"_id": &target.id
},
doc! {
"$pull": {
"relations": {
"_id": &user.id
}
}
},
None
)
) {
Ok(_) => Ok(json!({ "status": "None" })),
Err(_) => Err(Error::DatabaseError { operation: "update_one", with: "user" })
}
}
}
}

View File

@@ -0,0 +1,75 @@
use crate::{database::entities::RelationshipStatus, database::guards::reference::Ref, database::entities::User, database::permissions::get_relationship, util::result::Error, database::get_collection};
use rocket_contrib::json::JsonValue;
use crate::util::result::Result;
use mongodb::bson::doc;
use futures::try_join;
#[delete("/<target>/block")]
pub async fn req(user: User, target: Ref) -> Result<JsonValue> {
let col = get_collection("users");
match get_relationship(&user, &target) {
RelationshipStatus::None |
RelationshipStatus::User |
RelationshipStatus::BlockedOther |
RelationshipStatus::Incoming |
RelationshipStatus::Outgoing |
RelationshipStatus::Friend => Err(Error::NoEffect),
RelationshipStatus::Blocked => {
match get_relationship(&target.fetch_user().await?, &user.as_ref()) {
RelationshipStatus::Blocked => {
col.update_one(
doc! {
"_id": &user.id,
"relations._id": &target.id
},
doc! {
"$set": {
"relations.$.status": "BlockedOther"
}
},
None
)
.await
.map_err(|_| Error::DatabaseError { operation: "update_one", with: "user" })?;
Ok(json!({ "status": "BlockedOther" }))
},
RelationshipStatus::BlockedOther => {
match try_join!(
col.update_one(
doc! {
"_id": &user.id
},
doc! {
"$pull": {
"relations": {
"_id": &target.id
}
}
},
None
),
col.update_one(
doc! {
"_id": &target.id
},
doc! {
"$pull": {
"relations": {
"_id": &user.id
}
}
},
None
)
) {
Ok(_) => Ok(json!({ "status": "None" })),
Err(_) => Err(Error::DatabaseError { operation: "update_one", with: "user" })
}
},
_ => Err(Error::InternalError)
}
}
}
}

View File

@@ -11,60 +11,35 @@ use json;
#[serde(tag = "type")]
pub enum Error {
#[snafu(display("This error has not been labelled."))]
#[serde(rename = "unlabelled_error")]
LabelMe,
// ? Onboarding related errors.
#[snafu(display("Already finished onboarding."))]
#[serde(rename = "already_onboarded")]
AlreadyOnboarded,
// ? User related errors.
#[snafu(display("Username has already been taken."))]
#[serde(rename = "username_taken")]
UsernameTaken,
#[snafu(display("This user does not exist!"))]
#[serde(rename = "unknown_user")]
UnknownUser,
#[snafu(display("Already friends with this user."))]
AlreadyFriends,
#[snafu(display("Already sent a request to this user."))]
AlreadySentRequest,
#[snafu(display("You have blocked this user."))]
Blocked,
#[snafu(display("You have been blocked by this user."))]
BlockedByOther,
// ? General errors.
#[snafu(display("Failed to validate fields."))]
#[serde(rename = "failed_validation")]
FailedValidation { error: ValidationErrors },
#[snafu(display("Encountered a database error."))]
#[serde(rename = "database_error")]
DatabaseError { operation: &'static str, with: &'static str },
/* #[snafu(display("Failed to validate fields."))]
#[serde(rename = "failed_validation")]
FailedValidation { error: ValidationErrors },
#[snafu(display("Encountered a database error."))]
#[serde(rename = "database_error")]
DatabaseError,
#[snafu(display("Encountered an internal error."))]
#[serde(rename = "internal_error")]
#[snafu(display("Internal server error."))]
InternalError,
#[snafu(display("Operation did not succeed."))]
#[serde(rename = "operation_failed")]
OperationFailed,
#[snafu(display("Missing authentication headers."))]
#[serde(rename = "missing_headers")]
MissingHeaders,
#[snafu(display("Invalid session information."))]
#[serde(rename = "invalid_session")]
InvalidSession,
#[snafu(display("User account has not been verified."))]
#[serde(rename = "unverified_account")]
UnverifiedAccount,
#[snafu(display("This user does not exist!"))]
#[serde(rename = "unknown_user")]
UnknownUser,
#[snafu(display("Email is use."))]
#[serde(rename = "email_in_use")]
EmailInUse,
#[snafu(display("Wrong password."))]
#[serde(rename = "wrong_password")]
WrongPassword, */
#[snafu(display("This request had no effect."))]
NoEffect,
}
pub type Result<T, E = Error> = std::result::Result<T, E>;
@@ -73,12 +48,21 @@ pub type Result<T, E = Error> = std::result::Result<T, E>;
impl<'r> Responder<'r, 'static> for Error {
fn respond_to(self, _: &'r Request<'_>) -> response::Result<'static> {
let status = match self {
Error::AlreadyOnboarded => Status::Forbidden,
Error::DatabaseError { .. } => Status::InternalServerError,
Error::FailedValidation { .. } => Status::UnprocessableEntity,
Error::LabelMe => Status::InternalServerError,
Error::AlreadyOnboarded => Status::Forbidden,
Error::UnknownUser => Status::NotFound,
Error::UsernameTaken => Status::Conflict,
Error::AlreadyFriends => Status::Conflict,
Error::AlreadySentRequest => Status::Conflict,
Error::Blocked => Status::Conflict,
Error::BlockedByOther => Status::Forbidden,
Error::FailedValidation { .. } => Status::UnprocessableEntity,
Error::DatabaseError { .. } => Status::InternalServerError,
Error::InternalError => Status::InternalServerError,
Error::NoEffect => Status::Ok,
};
// Serialize the error data structure into JSON.