Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
No results found
Show changes
Showing
with 915 additions and 244 deletions
use std::collections::HashMap;
use crate::database::*;
use crate::util::result::{Error, Result};
use mongodb::bson::doc;
use rocket_contrib::json::{Json, JsonValue};
use serde::{Deserialize, Serialize};
use ulid::Ulid;
use validator::Validate;
#[derive(Validate, Serialize, Deserialize)]
pub struct Data {
#[validate(length(min = 1, max = 32))]
name: String,
#[validate(length(min = 0, max = 1024))]
description: Option<String>,
// Maximum length of 36 allows both ULIDs and UUIDs.
#[validate(length(min = 1, max = 36))]
nonce: String,
}
#[post("/create", data = "<info>")]
pub async fn req(user: User, info: Json<Data>) -> Result<JsonValue> {
let info = info.into_inner();
info.validate()
.map_err(|error| Error::FailedValidation { error })?;
if get_collection("servers")
.find_one(
doc! {
"nonce": &info.nonce
},
None,
)
.await
.map_err(|_| Error::DatabaseError {
operation: "find_one",
with: "server",
})?
.is_some()
{
Err(Error::DuplicateNonce)?
}
let id = Ulid::new().to_string();
let cid = Ulid::new().to_string();
let server = Server {
id: id.clone(),
nonce: Some(info.nonce.clone()),
owner: user.id.clone(),
name: info.name,
description: info.description,
channels: vec![cid.clone()],
categories: None,
system_messages: Some(SystemMessageChannels {
user_joined: Some(cid.clone()),
user_left: Some(cid.clone()),
user_kicked: Some(cid.clone()),
user_banned: Some(cid.clone()),
}),
roles: HashMap::new(),
default_permissions: (
*permissions::server::DEFAULT_PERMISSION as i32,
*permissions::channel::DEFAULT_PERMISSION_SERVER as i32
),
icon: None,
banner: None,
};
Channel::TextChannel {
id: cid,
server: id,
nonce: Some(info.nonce),
name: "general".to_string(),
description: None,
icon: None,
last_message: None,
default_permissions: None,
role_permissions: HashMap::new()
}
.publish()
.await?;
server.clone().create().await?;
server.join_member(&user.id).await?;
Ok(json!(server))
}
use crate::database::*;
use crate::util::result::{Error, Result};
use mongodb::bson::doc;
#[delete("/<target>")]
pub async fn req(user: User, target: Ref) -> Result<()> {
let target = target.fetch_server().await?;
let perm = permissions::PermissionCalculator::new(&user)
.with_server(&target)
.for_server()
.await?;
if !perm.get_view() {
return Err(Error::MissingPermission);
}
if user.id == target.owner {
target.delete().await
} else {
target.remove_member(&user.id, RemoveMember::Leave).await
}
}
use crate::notifications::events::ClientboundNotification;
use crate::util::result::{Error, Result};
use crate::{database::*, notifications::events::RemoveServerField};
use mongodb::bson::{doc, to_bson, to_document};
use rocket_contrib::json::Json;
use serde::{Deserialize, Serialize};
use validator::Validate;
#[derive(Validate, Serialize, Deserialize)]
pub struct Data {
#[validate(length(min = 1, max = 32))]
name: Option<String>,
#[validate(length(min = 0, max = 1024))]
description: Option<String>,
icon: Option<String>,
banner: Option<String>,
categories: Option<Vec<Category>>,
system_messages: Option<SystemMessageChannels>,
remove: Option<RemoveServerField>,
}
#[patch("/<target>", data = "<data>")]
pub async fn req(user: User, target: Ref, data: Json<Data>) -> Result<()> {
let data = data.into_inner();
data.validate()
.map_err(|error| Error::FailedValidation { error })?;
if data.name.is_none() && data.description.is_none() && data.icon.is_none() && data.banner.is_none() && data.remove.is_none() && data.categories.is_none() && data.system_messages.is_none()
{
return Ok(());
}
let target = target.fetch_server().await?;
let perm = permissions::PermissionCalculator::new(&user)
.with_server(&target)
.for_server()
.await?;
if !perm.get_manage_server() {
Err(Error::MissingPermission)?
}
let mut set = doc! {};
let mut unset = doc! {};
let mut remove_icon = false;
let mut remove_banner = false;
if let Some(remove) = &data.remove {
match remove {
RemoveServerField::Icon => {
unset.insert("icon", 1);
remove_icon = true;
}
RemoveServerField::Banner => {
unset.insert("banner", 1);
remove_banner = true;
}
RemoveServerField::Description => {
unset.insert("description", 1);
}
}
}
if let Some(name) = &data.name {
set.insert("name", name);
}
if let Some(description) = &data.description {
set.insert("description", description);
}
if let Some(attachment_id) = &data.icon {
let attachment = File::find_and_use(&attachment_id, "icons", "object", &target.id).await?;
set.insert(
"icon",
to_document(&attachment).map_err(|_| Error::DatabaseError {
operation: "to_document",
with: "attachment",
})?,
);
remove_icon = true;
}
if let Some(attachment_id) = &data.banner {
let attachment =
File::find_and_use(&attachment_id, "banners", "server", &target.id).await?;
set.insert(
"banner",
to_document(&attachment).map_err(|_| Error::DatabaseError {
operation: "to_document",
with: "attachment",
})?,
);
remove_banner = true;
}
if let Some(categories) = &data.categories {
set.insert("categories", to_bson(&categories).map_err(|_| Error::DatabaseError { operation: "to_document", with: "categories" })?);
}
if let Some(system_messages) = &data.system_messages {
set.insert("system_messages", to_bson(&system_messages).map_err(|_| Error::DatabaseError { operation: "to_document", with: "system_messages" })?);
}
let mut operations = doc! {};
if set.len() > 0 {
operations.insert("$set", &set);
}
if unset.len() > 0 {
operations.insert("$unset", unset);
}
if operations.len() > 0 {
get_collection("servers")
.update_one(doc! { "_id": &target.id }, operations, None)
.await
.map_err(|_| Error::DatabaseError {
operation: "update_one",
with: "server",
})?;
}
ClientboundNotification::ServerUpdate {
id: target.id.clone(),
data: json!(set),
clear: data.remove,
}
.publish(target.id.clone());
let Server { icon, banner, .. } = target;
if remove_icon {
if let Some(old_icon) = icon {
old_icon.delete().await?;
}
}
if remove_banner {
if let Some(old_banner) = banner {
old_banner.delete().await?;
}
}
Ok(())
}
use crate::database::*;
use crate::util::result::{Error, Result};
use rocket_contrib::json::JsonValue;
#[get("/<target>")]
pub async fn req(user: User, target: Ref) -> Result<JsonValue> {
let target = target.fetch_server().await?;
let perm = permissions::PermissionCalculator::new(&user)
.with_server(&target)
.for_server()
.await?;
if !perm.get_view() {
Err(Error::MissingPermission)?
}
Ok(json!(target))
}
use crate::database::*;
use crate::util::result::{Error, Result};
use mongodb::bson::doc;
use mongodb::options::FindOneOptions;
use rocket_contrib::json::{Json, JsonValue};
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
pub struct Options {
keys: Vec<String>,
}
#[post("/settings/fetch", data = "<options>")]
pub async fn req(user: User, options: Json<Options>) -> Result<JsonValue> {
let options = options.into_inner();
let mut projection = doc! {
"_id": 0,
};
for key in options.keys {
projection.insert(key, 1);
}
if let Some(doc) = get_collection("user_settings")
.find_one(
doc! {
"_id": user.id
},
FindOneOptions::builder().projection(projection).build(),
)
.await
.map_err(|_| Error::DatabaseError {
operation: "find_one",
with: "user_settings",
})?
{
Ok(json!(doc))
} else {
Ok(json!({}))
}
}
use crate::database::*;
use crate::util::result::Result;
use mongodb::bson::doc;
use rocket_contrib::json::JsonValue;
#[get("/unreads")]
pub async fn req(user: User) -> Result<JsonValue> {
Ok(json!(User::fetch_unreads(&user.id).await?))
}
use rocket::Route; use rocket::Route;
mod get_settings;
mod get_unreads;
mod set_settings;
pub fn routes() -> Vec<Route> { pub fn routes() -> Vec<Route> {
routes![] routes![get_settings::req, set_settings::req, get_unreads::req]
} }
use crate::database::*;
use crate::notifications::events::ClientboundNotification;
use crate::util::result::{Error, Result};
use chrono::prelude::*;
use mongodb::bson::{doc, to_bson};
use mongodb::options::UpdateOptions;
use rocket::request::Form;
use rocket_contrib::json::Json;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
type Data = HashMap<String, String>;
#[derive(Serialize, Deserialize, FromForm)]
pub struct Options {
timestamp: Option<i64>,
}
#[post("/settings/set?<options..>", data = "<data>")]
pub async fn req(user: User, data: Json<Data>, options: Form<Options>) -> Result<()> {
let data = data.into_inner();
let current_time = Utc::now().timestamp_millis();
let timestamp = if let Some(timestamp) = options.timestamp {
if timestamp > current_time {
current_time
} else {
timestamp
}
} else {
current_time
};
let mut set = doc! {};
for (key, data) in &data {
set.insert(
key.clone(),
vec![
to_bson(&timestamp).unwrap(),
to_bson(&data.clone()).unwrap(),
],
);
}
if set.len() > 0 {
get_collection("user_settings")
.update_one(
doc! {
"_id": &user.id
},
doc! {
"$set": &set
},
UpdateOptions::builder().upsert(true).build(),
)
.await
.map_err(|_| Error::DatabaseError {
operation: "update_one",
with: "user_settings",
})?;
}
ClientboundNotification::UserSettingsUpdate {
id: user.id.clone(),
update: json!(set),
}
.publish(user.id);
Ok(())
}
...@@ -31,6 +31,8 @@ pub async fn req(user: User, username: String) -> Result<JsonValue> { ...@@ -31,6 +31,8 @@ pub async fn req(user: User, username: String) -> Result<JsonValue> {
with: "user", with: "user",
})?; })?;
let target_user = Ref::from(target_id.to_string())?.fetch_user().await?;
match get_relationship(&user, &target_id) { match get_relationship(&user, &target_id) {
RelationshipStatus::User => return Err(Error::NoEffect), RelationshipStatus::User => return Err(Error::NoEffect),
RelationshipStatus::Friend => return Err(Error::AlreadyFriends), RelationshipStatus::Friend => return Err(Error::AlreadyFriends),
...@@ -65,21 +67,26 @@ pub async fn req(user: User, username: String) -> Result<JsonValue> { ...@@ -65,21 +67,26 @@ pub async fn req(user: User, username: String) -> Result<JsonValue> {
) )
) { ) {
Ok(_) => { Ok(_) => {
try_join!( let target_user = target_user
ClientboundNotification::UserRelationship { .from_override(&user, RelationshipStatus::Friend)
id: user.id.clone(), .await?;
user: target_id.to_string(), let user = user
status: RelationshipStatus::Friend .from_override(&target_user, RelationshipStatus::Friend)
} .await?;
.publish(user.id.clone()),
ClientboundNotification::UserRelationship { ClientboundNotification::UserRelationship {
id: target_id.to_string(), id: user.id.clone(),
user: user.id.clone(), user: target_user,
status: RelationshipStatus::Friend status: RelationshipStatus::Friend,
} }
.publish(target_id.to_string()) .publish(user.id.clone());
)
.ok(); ClientboundNotification::UserRelationship {
id: target_id.to_string(),
user,
status: RelationshipStatus::Friend,
}
.publish(target_id.to_string());
Ok(json!({ "status": "Friend" })) Ok(json!({ "status": "Friend" }))
} }
...@@ -121,21 +128,26 @@ pub async fn req(user: User, username: String) -> Result<JsonValue> { ...@@ -121,21 +128,26 @@ pub async fn req(user: User, username: String) -> Result<JsonValue> {
) )
) { ) {
Ok(_) => { Ok(_) => {
try_join!( let target_user = target_user
ClientboundNotification::UserRelationship { .from_override(&user, RelationshipStatus::Outgoing)
id: user.id.clone(), .await?;
user: target_id.to_string(), let user = user
status: RelationshipStatus::Outgoing .from_override(&target_user, RelationshipStatus::Incoming)
} .await?;
.publish(user.id.clone()),
ClientboundNotification::UserRelationship { ClientboundNotification::UserRelationship {
id: target_id.to_string(), id: user.id.clone(),
user: user.id.clone(), user: target_user,
status: RelationshipStatus::Incoming status: RelationshipStatus::Outgoing,
} }
.publish(target_id.to_string()) .publish(user.id.clone());
)
.ok(); ClientboundNotification::UserRelationship {
id: target_id.to_string(),
user,
status: RelationshipStatus::Incoming,
}
.publish(target_id.to_string());
Ok(json!({ "status": "Outgoing" })) Ok(json!({ "status": "Outgoing" }))
} }
......
...@@ -10,6 +10,8 @@ use rocket_contrib::json::JsonValue; ...@@ -10,6 +10,8 @@ use rocket_contrib::json::JsonValue;
pub async fn req(user: User, target: Ref) -> Result<JsonValue> { pub async fn req(user: User, target: Ref) -> Result<JsonValue> {
let col = get_collection("users"); let col = get_collection("users");
let target = target.fetch_user().await?;
match get_relationship(&user, &target.id) { match get_relationship(&user, &target.id) {
RelationshipStatus::User | RelationshipStatus::Blocked => Err(Error::NoEffect), RelationshipStatus::User | RelationshipStatus::Blocked => Err(Error::NoEffect),
RelationshipStatus::BlockedOther => { RelationshipStatus::BlockedOther => {
...@@ -33,12 +35,10 @@ pub async fn req(user: User, target: Ref) -> Result<JsonValue> { ...@@ -33,12 +35,10 @@ pub async fn req(user: User, target: Ref) -> Result<JsonValue> {
ClientboundNotification::UserRelationship { ClientboundNotification::UserRelationship {
id: user.id.clone(), id: user.id.clone(),
user: target.id.clone(), user: target,
status: RelationshipStatus::Blocked, status: RelationshipStatus::Blocked,
} }
.publish(user.id.clone()) .publish(user.id.clone());
.await
.ok();
Ok(json!({ "status": "Blocked" })) Ok(json!({ "status": "Blocked" }))
} }
...@@ -74,21 +74,27 @@ pub async fn req(user: User, target: Ref) -> Result<JsonValue> { ...@@ -74,21 +74,27 @@ pub async fn req(user: User, target: Ref) -> Result<JsonValue> {
) )
) { ) {
Ok(_) => { Ok(_) => {
try_join!( let target = target
ClientboundNotification::UserRelationship { .from_override(&user, RelationshipStatus::Blocked)
id: user.id.clone(), .await?;
user: target.id.clone(), let user = user
status: RelationshipStatus::Blocked .from_override(&target, RelationshipStatus::BlockedOther)
} .await?;
.publish(user.id.clone()), let target_id = target.id.clone();
ClientboundNotification::UserRelationship {
id: target.id.clone(), ClientboundNotification::UserRelationship {
user: user.id.clone(), id: user.id.clone(),
status: RelationshipStatus::BlockedOther user: target,
} status: RelationshipStatus::Blocked,
.publish(target.id.clone()) }
) .publish(user.id.clone());
.ok();
ClientboundNotification::UserRelationship {
id: target_id.clone(),
user,
status: RelationshipStatus::BlockedOther,
}
.publish(target_id);
Ok(json!({ "status": "Blocked" })) Ok(json!({ "status": "Blocked" }))
} }
...@@ -128,21 +134,27 @@ pub async fn req(user: User, target: Ref) -> Result<JsonValue> { ...@@ -128,21 +134,27 @@ pub async fn req(user: User, target: Ref) -> Result<JsonValue> {
) )
) { ) {
Ok(_) => { Ok(_) => {
try_join!( let target = target
ClientboundNotification::UserRelationship { .from_override(&user, RelationshipStatus::Blocked)
id: user.id.clone(), .await?;
user: target.id.clone(), let user = user
status: RelationshipStatus::Blocked .from_override(&target, RelationshipStatus::BlockedOther)
} .await?;
.publish(user.id.clone()), let target_id = target.id.clone();
ClientboundNotification::UserRelationship {
id: target.id.clone(), ClientboundNotification::UserRelationship {
user: user.id.clone(), id: user.id.clone(),
status: RelationshipStatus::BlockedOther user: target,
} status: RelationshipStatus::Blocked,
.publish(target.id.clone()) }
) .publish(user.id.clone());
.ok();
ClientboundNotification::UserRelationship {
id: target_id.clone(),
user,
status: RelationshipStatus::BlockedOther,
}
.publish(target_id);
Ok(json!({ "status": "Blocked" })) Ok(json!({ "status": "Blocked" }))
} }
......
use crate::database::*;
use crate::notifications::events::ClientboundNotification;
use crate::util::result::{Error, Result};
use mongodb::bson::doc;
use rauth::auth::{Auth, Session};
use regex::Regex;
use rocket::State;
use rocket_contrib::json::Json;
use serde::{Deserialize, Serialize};
use validator::Validate;
// ! FIXME: should be global somewhere; maybe use config(?)
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), regex = "RE_USERNAME")]
username: Option<String>,
#[validate(length(min = 8, max = 72))]
password: String,
}
#[patch("/<_ignore_id>/username", data = "<data>")]
pub async fn req(
auth: State<'_, Auth>,
session: Session,
user: User,
data: Json<Data>,
_ignore_id: String,
) -> Result<()> {
data.validate()
.map_err(|error| Error::FailedValidation { error })?;
auth.verify_password(&session, data.password.clone())
.await
.map_err(|_| Error::InvalidCredentials)?;
let mut set = doc! {};
if let Some(username) = &data.username {
if User::is_username_taken(&username).await? {
return Err(Error::UsernameTaken);
}
set.insert("username", username.clone());
}
get_collection("users")
.update_one(doc! { "_id": &user.id }, doc! { "$set": set }, None)
.await
.map_err(|_| Error::DatabaseError {
operation: "update_one",
with: "user",
})?;
ClientboundNotification::UserUpdate {
id: user.id.clone(),
data: json!({
"username": data.username
}),
clear: None,
}
.publish_as_user(user.id.clone());
Ok(())
}
use crate::notifications::events::ClientboundNotification;
use crate::util::result::{Error, Result};
use crate::{database::*, notifications::events::RemoveUserField};
use mongodb::bson::{doc, to_document};
use rocket_contrib::json::Json;
use serde::{Deserialize, Serialize};
use validator::Validate;
#[derive(Validate, Serialize, Deserialize, Debug)]
pub struct UserProfileData {
#[validate(length(min = 0, max = 2000))]
#[serde(skip_serializing_if = "Option::is_none")]
content: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[validate(length(min = 1, max = 128))]
background: Option<String>,
}
#[derive(Validate, Serialize, Deserialize)]
pub struct Data {
#[validate]
status: Option<UserStatus>,
#[validate]
profile: Option<UserProfileData>,
#[validate(length(min = 1, max = 128))]
avatar: Option<String>,
remove: Option<RemoveUserField>,
}
#[patch("/<_ignore_id>", data = "<data>")]
pub async fn req(user: User, data: Json<Data>, _ignore_id: String) -> Result<()> {
let mut data = data.into_inner();
data.validate()
.map_err(|error| Error::FailedValidation { error })?;
if data.status.is_none()
&& data.profile.is_none()
&& data.avatar.is_none()
&& data.remove.is_none()
{
return Ok(());
}
let mut unset = doc! {};
let mut set = doc! {};
let mut remove_background = false;
let mut remove_avatar = false;
if let Some(remove) = &data.remove {
match remove {
RemoveUserField::ProfileContent => {
unset.insert("profile.content", 1);
}
RemoveUserField::ProfileBackground => {
unset.insert("profile.background", 1);
remove_background = true;
}
RemoveUserField::StatusText => {
unset.insert("status.text", 1);
}
RemoveUserField::Avatar => {
unset.insert("avatar", 1);
remove_avatar = true;
}
}
}
if let Some(status) = &data.status {
set.insert(
"status",
to_document(&status).map_err(|_| Error::DatabaseError {
operation: "to_document",
with: "status",
})?,
);
}
if let Some(profile) = data.profile {
if let Some(content) = profile.content {
set.insert("profile.content", content);
}
if let Some(attachment_id) = profile.background {
let attachment =
File::find_and_use(&attachment_id, "backgrounds", "user", &user.id).await?;
set.insert(
"profile.background",
to_document(&attachment).map_err(|_| Error::DatabaseError {
operation: "to_document",
with: "attachment",
})?,
);
remove_background = true;
}
}
let avatar = std::mem::replace(&mut data.avatar, None);
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",
})?,
);
remove_avatar = true;
}
let mut operations = doc! {};
if set.len() > 0 {
operations.insert("$set", &set);
}
if unset.len() > 0 {
operations.insert("$unset", unset);
}
if operations.len() > 0 {
get_collection("users")
.update_one(doc! { "_id": &user.id }, operations, None)
.await
.map_err(|_| Error::DatabaseError {
operation: "update_one",
with: "user",
})?;
}
ClientboundNotification::UserUpdate {
id: user.id.clone(),
data: json!(set),
clear: data.remove,
}
.publish_as_user(user.id.clone());
if remove_avatar {
if let Some(old_avatar) = user.avatar {
old_avatar.delete().await?;
}
}
if remove_background {
if let Some(profile) = user.profile {
if let Some(old_background) = profile.background {
old_background.delete().await?;
}
}
}
Ok(())
}
use crate::database::*;
use crate::util::result::{Error, Result};
use mongodb::bson::doc;
use rocket_contrib::json::JsonValue;
#[get("/<target>/profile")]
pub async fn req(user: User, target: Ref) -> Result<JsonValue> {
let target = target.fetch_user().await?;
let perm = permissions::PermissionCalculator::new(&user)
.with_user(&target)
.for_user_given()
.await?;
if !perm.get_view_profile() {
Err(Error::MissingPermission)?
}
if target.profile.is_some() {
Ok(json!(target.profile))
} else {
Ok(json!({}))
}
}
...@@ -12,8 +12,8 @@ pub async fn req(user: User, target: Ref) -> Result<JsonValue> { ...@@ -12,8 +12,8 @@ pub async fn req(user: User, target: Ref) -> Result<JsonValue> {
.find( .find(
doc! { doc! {
"$and": [ "$and": [
{ "relations._id": &user.id, "relations.status": "Friend" }, { "relations": { "$elemMatch": { "_id": &user.id, "status": "Friend" } } },
{ "relations._id": &target.id, "relations.status": "Friend" } { "relations": { "$elemMatch": { "_id": &target.id, "status": "Friend" } } }
] ]
}, },
FindOptions::builder().projection(doc! { "_id": 1 }).build(), FindOptions::builder().projection(doc! { "_id": 1 }).build(),
......
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))
}
use rocket::response::NamedFile; use rocket::{Request, Response};
use rocket::response::{self, NamedFile, Responder};
use std::path::Path; use std::path::Path;
use crate::database::Ref; use crate::database::Ref;
pub struct CachedFile(NamedFile);
pub static CACHE_CONTROL: &'static str = "public, max-age=31536000, immutable";
impl<'r> Responder<'r, 'static> for CachedFile {
fn respond_to(self, req: &'r Request<'_>) -> response::Result<'static> {
Response::build_from(self.0.respond_to(req)?)
.raw_header("Cache-control", CACHE_CONTROL)
.ok()
}
}
#[get("/<target>/default_avatar")] #[get("/<target>/default_avatar")]
pub async fn req(target: Ref) -> Option<NamedFile> { pub async fn req(target: Ref) -> Option<CachedFile> {
match target.id.chars().nth(25).unwrap() { match target.id.chars().nth(25).unwrap() {
'0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' => { '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' => {
NamedFile::open(Path::new("assets/user_red.png")).await.ok() NamedFile::open(Path::new("assets/user_red.png")).await.ok().map(|n| CachedFile(n))
} }
'8' | '9' | 'A' | 'C' | 'B' | 'D' | 'E' | 'F' => { '8' | '9' | 'A' | 'C' | 'B' | 'D' | 'E' | 'F' => {
NamedFile::open(Path::new("assets/user_green.png")) NamedFile::open(Path::new("assets/user_green.png"))
.await .await
.ok() .ok().map(|n| CachedFile(n))
} }
'G' | 'H' | 'J' | 'K' | 'M' | 'N' | 'P' | 'Q' => { 'G' | 'H' | 'J' | 'K' | 'M' | 'N' | 'P' | 'Q' => {
NamedFile::open(Path::new("assets/user_blue.png")) NamedFile::open(Path::new("assets/user_blue.png"))
.await .await
.ok() .ok().map(|n| CachedFile(n))
} }
'R' | 'S' | 'T' | 'V' | 'W' | 'X' | 'Y' | 'Z' => { 'R' | 'S' | 'T' | 'V' | 'W' | 'X' | 'Y' | 'Z' => {
NamedFile::open(Path::new("assets/user_yellow.png")) NamedFile::open(Path::new("assets/user_yellow.png"))
.await .await
.ok() .ok().map(|n| CachedFile(n))
} }
_ => unreachable!(), _ => unreachable!(),
} }
......
...@@ -2,12 +2,14 @@ use rocket::Route; ...@@ -2,12 +2,14 @@ use rocket::Route;
mod add_friend; mod add_friend;
mod block_user; mod block_user;
mod change_username;
mod edit_user;
mod fetch_dms; mod fetch_dms;
mod fetch_profile;
mod fetch_relationship; mod fetch_relationship;
mod fetch_relationships; mod fetch_relationships;
mod fetch_user; mod fetch_user;
mod find_mutual; mod find_mutual;
mod get_avatar;
mod get_default_avatar; mod get_default_avatar;
mod open_dm; mod open_dm;
mod remove_friend; mod remove_friend;
...@@ -17,8 +19,10 @@ pub fn routes() -> Vec<Route> { ...@@ -17,8 +19,10 @@ pub fn routes() -> Vec<Route> {
routes![ routes![
// User Information // User Information
fetch_user::req, fetch_user::req,
edit_user::req,
change_username::req,
get_default_avatar::req, get_default_avatar::req,
get_avatar::req, fetch_profile::req,
// Direct Messaging // Direct Messaging
fetch_dms::req, fetch_dms::req,
open_dm::req, open_dm::req,
......
...@@ -10,6 +10,8 @@ use rocket_contrib::json::JsonValue; ...@@ -10,6 +10,8 @@ use rocket_contrib::json::JsonValue;
pub async fn req(user: User, target: Ref) -> Result<JsonValue> { pub async fn req(user: User, target: Ref) -> Result<JsonValue> {
let col = get_collection("users"); let col = get_collection("users");
let target = target.fetch_user().await?;
match get_relationship(&user, &target.id) { match get_relationship(&user, &target.id) {
RelationshipStatus::Friend RelationshipStatus::Friend
| RelationshipStatus::Outgoing | RelationshipStatus::Outgoing
...@@ -43,21 +45,27 @@ pub async fn req(user: User, target: Ref) -> Result<JsonValue> { ...@@ -43,21 +45,27 @@ pub async fn req(user: User, target: Ref) -> Result<JsonValue> {
) )
) { ) {
Ok(_) => { Ok(_) => {
try_join!( let target = target
ClientboundNotification::UserRelationship { .from_override(&user, RelationshipStatus::None)
id: user.id.clone(), .await?;
user: target.id.clone(), let user = user
status: RelationshipStatus::None .from_override(&target, RelationshipStatus::None)
} .await?;
.publish(user.id.clone()), let target_id = target.id.clone();
ClientboundNotification::UserRelationship {
id: target.id.clone(), ClientboundNotification::UserRelationship {
user: user.id.clone(), id: user.id.clone(),
status: RelationshipStatus::None user: target,
} status: RelationshipStatus::None,
.publish(target.id.clone()) }
) .publish(user.id.clone());
.ok();
ClientboundNotification::UserRelationship {
id: target_id.clone(),
user,
status: RelationshipStatus::None,
}
.publish(target_id);
Ok(json!({ "status": "None" })) Ok(json!({ "status": "None" }))
} }
......
...@@ -9,97 +9,103 @@ use rocket_contrib::json::JsonValue; ...@@ -9,97 +9,103 @@ use rocket_contrib::json::JsonValue;
#[delete("/<target>/block")] #[delete("/<target>/block")]
pub async fn req(user: User, target: Ref) -> Result<JsonValue> { pub async fn req(user: User, target: Ref) -> Result<JsonValue> {
let col = get_collection("users"); let col = get_collection("users");
let target = target.fetch_user().await?;
match get_relationship(&user, &target.id) { match get_relationship(&user, &target.id) {
RelationshipStatus::Blocked => { RelationshipStatus::Blocked => match get_relationship(&target, &user.id) {
match get_relationship(&target.fetch_user().await?, &user.id) { RelationshipStatus::Blocked => {
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",
})?;
let target = target
.from_override(&user, RelationshipStatus::BlockedOther)
.await?;
ClientboundNotification::UserRelationship {
id: user.id.clone(),
user: target,
status: RelationshipStatus::BlockedOther,
}
.publish(user.id.clone());
Ok(json!({ "status": "BlockedOther" }))
}
RelationshipStatus::BlockedOther => {
match try_join!(
col.update_one( col.update_one(
doc! { doc! {
"_id": &user.id, "_id": &user.id
"relations._id": &target.id
}, },
doc! { doc! {
"$set": { "$pull": {
"relations.$.status": "BlockedOther" "relations": {
"_id": &target.id
}
}
},
None
),
col.update_one(
doc! {
"_id": &target.id
},
doc! {
"$pull": {
"relations": {
"_id": &user.id
}
} }
}, },
None, None
) )
.await ) {
.map_err(|_| Error::DatabaseError { Ok(_) => {
operation: "update_one", let target = target
with: "user", .from_override(&user, RelationshipStatus::None)
})?; .await?;
let user = user
ClientboundNotification::UserRelationship { .from_override(&target, RelationshipStatus::None)
id: user.id.clone(), .await?;
user: target.id.clone(), let target_id = target.id.clone();
status: RelationshipStatus::BlockedOther,
}
.publish(user.id.clone())
.await
.ok();
Ok(json!({ "status": "BlockedOther" })) ClientboundNotification::UserRelationship {
} id: user.id.clone(),
RelationshipStatus::BlockedOther => { user: target,
match try_join!( status: RelationshipStatus::None,
col.update_one( }
doc! { .publish(user.id.clone());
"_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(_) => {
try_join!(
ClientboundNotification::UserRelationship {
id: user.id.clone(),
user: target.id.clone(),
status: RelationshipStatus::None
}
.publish(user.id.clone()),
ClientboundNotification::UserRelationship {
id: target.id.clone(),
user: user.id.clone(),
status: RelationshipStatus::None
}
.publish(target.id.clone())
)
.ok();
Ok(json!({ "status": "None" })) ClientboundNotification::UserRelationship {
id: target_id.clone(),
user: user,
status: RelationshipStatus::None,
} }
Err(_) => Err(Error::DatabaseError { .publish(target_id);
operation: "update_one",
with: "user", Ok(json!({ "status": "None" }))
}),
} }
Err(_) => Err(Error::DatabaseError {
operation: "update_one",
with: "user",
}),
} }
_ => Err(Error::InternalError),
} }
} _ => Err(Error::InternalError),
},
_ => Err(Error::NoEffect), _ => Err(Error::NoEffect),
} }
} }
...@@ -3,75 +3,63 @@ use rocket::http::{ContentType, Status}; ...@@ -3,75 +3,63 @@ use rocket::http::{ContentType, Status};
use rocket::request::Request; use rocket::request::Request;
use rocket::response::{self, Responder, Response}; use rocket::response::{self, Responder, Response};
use serde::Serialize; use serde::Serialize;
use snafu::Snafu;
use std::io::Cursor; use std::io::Cursor;
use validator::ValidationErrors; use validator::ValidationErrors;
#[derive(Serialize, Debug, Snafu)] #[derive(Serialize, Debug)]
#[serde(tag = "type")] #[serde(tag = "type")]
pub enum Error { pub enum Error {
#[snafu(display("This error has not been labelled."))]
LabelMe, LabelMe,
// ? Onboarding related errors. // ? Onboarding related errors.
#[snafu(display("Already finished onboarding."))]
AlreadyOnboarded, AlreadyOnboarded,
// ? User related errors. // ? User related errors.
#[snafu(display("Username has already been taken."))]
UsernameTaken, UsernameTaken,
#[snafu(display("This user does not exist!"))]
UnknownUser, UnknownUser,
#[snafu(display("Already friends with this user."))]
AlreadyFriends, AlreadyFriends,
#[snafu(display("Already sent a request to this user."))]
AlreadySentRequest, AlreadySentRequest,
#[snafu(display("You have blocked this user."))]
Blocked, Blocked,
#[snafu(display("You have been blocked by this user."))]
BlockedByOther, BlockedByOther,
#[snafu(display("Not friends with target user."))]
NotFriends, NotFriends,
// ? Channel related errors. // ? Channel related errors.
#[snafu(display("This channel does not exist!"))]
UnknownChannel, UnknownChannel,
#[snafu(display("Attachment does not exist!"))]
UnknownAttachment, UnknownAttachment,
#[snafu(display("Cannot edit someone else's message."))] UnknownMessage,
CannotEditMessage, CannotEditMessage,
#[snafu(display("Cannot send empty message."))] CannotJoinCall,
TooManyAttachments,
TooManyReplies,
EmptyMessage, EmptyMessage,
#[snafu(display("Cannot remove yourself from a group, use delete channel instead."))]
CannotRemoveYourself, CannotRemoveYourself,
#[snafu(display("Group size is too large."))] GroupTooLarge {
GroupTooLarge { max: usize }, max: usize,
#[snafu(display("User already part of group."))] },
AlreadyInGroup, AlreadyInGroup,
#[snafu(display("User is not part of the group."))]
NotInGroup, NotInGroup,
// ? Server related errors.
UnknownServer,
InvalidRole,
Banned,
// ? General errors. // ? General errors.
#[snafu(display("Trying to fetch too much data."))]
TooManyIds, TooManyIds,
#[snafu(display("Failed to validate fields."))] FailedValidation {
FailedValidation { error: ValidationErrors }, error: ValidationErrors,
#[snafu(display("Encountered a database error."))] },
DatabaseError { DatabaseError {
operation: &'static str, operation: &'static str,
with: &'static str, with: &'static str,
}, },
#[snafu(display("Internal server error."))]
InternalError, InternalError,
#[snafu(display("Missing permission."))]
MissingPermission, MissingPermission,
#[snafu(display("Operation cannot be performed on this object."))]
InvalidOperation, InvalidOperation,
#[snafu(display("Already created an object with this nonce."))] InvalidCredentials,
DuplicateNonce, DuplicateNonce,
#[snafu(display("Voso is not enabled on this instance."))]
VosoUnavailable, VosoUnavailable,
#[snafu(display("This request had no effect."))] NotFound,
NoEffect, NoEffect,
} }
...@@ -94,22 +82,32 @@ impl<'r> Responder<'r, 'static> for Error { ...@@ -94,22 +82,32 @@ impl<'r> Responder<'r, 'static> for Error {
Error::NotFriends => Status::Forbidden, Error::NotFriends => Status::Forbidden,
Error::UnknownChannel => Status::NotFound, Error::UnknownChannel => Status::NotFound,
Error::UnknownMessage => Status::NotFound,
Error::UnknownAttachment => Status::BadRequest, Error::UnknownAttachment => Status::BadRequest,
Error::CannotEditMessage => Status::Forbidden, Error::CannotEditMessage => Status::Forbidden,
Error::CannotJoinCall => Status::BadRequest,
Error::TooManyAttachments => Status::BadRequest,
Error::TooManyReplies => Status::BadRequest,
Error::EmptyMessage => Status::UnprocessableEntity, Error::EmptyMessage => Status::UnprocessableEntity,
Error::CannotRemoveYourself => Status::BadRequest, Error::CannotRemoveYourself => Status::BadRequest,
Error::GroupTooLarge { .. } => Status::Forbidden, Error::GroupTooLarge { .. } => Status::Forbidden,
Error::AlreadyInGroup => Status::Conflict, Error::AlreadyInGroup => Status::Conflict,
Error::NotInGroup => Status::NotFound, Error::NotInGroup => Status::NotFound,
Error::UnknownServer => Status::NotFound,
Error::InvalidRole => Status::NotFound,
Error::Banned => Status::Forbidden,
Error::FailedValidation { .. } => Status::UnprocessableEntity, Error::FailedValidation { .. } => Status::UnprocessableEntity,
Error::DatabaseError { .. } => Status::InternalServerError, Error::DatabaseError { .. } => Status::InternalServerError,
Error::InternalError => Status::InternalServerError, Error::InternalError => Status::InternalServerError,
Error::MissingPermission => Status::Forbidden, Error::MissingPermission => Status::Forbidden,
Error::InvalidOperation => Status::BadRequest, Error::InvalidOperation => Status::BadRequest,
Error::TooManyIds => Status::BadRequest, Error::TooManyIds => Status::BadRequest,
Error::InvalidCredentials => Status::Forbidden,
Error::DuplicateNonce => Status::Conflict, Error::DuplicateNonce => Status::Conflict,
Error::VosoUnavailable => Status::BadRequest, Error::VosoUnavailable => Status::BadRequest,
Error::NotFound => Status::NotFound,
Error::NoEffect => Status::Ok, Error::NoEffect => Status::Ok,
}; };
......