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 1451 additions and 354 deletions
use crate::database::*;
use crate::util::result::{Error, Result};
use mongodb::bson::doc;
#[delete("/<target>/members/<member>")]
pub async fn req(user: User, target: Ref, member: String) -> Result<()> {
let target = target.fetch_server().await?;
let perm = permissions::PermissionCalculator::new(&user)
.with_server(&target)
.for_server()
.await?;
if !perm.get_kick_members() {
return Err(Error::MissingPermission);
}
let member = Ref::from(member)?.fetch_member(&target.id).await?;
if member.id.user == user.id {
return Err(Error::InvalidOperation);
}
if target.id == target.owner {
return Err(Error::MissingPermission);
}
target
.remove_member(&member.id.user, RemoveMember::Kick)
.await
}
use rocket::Route;
mod server_create;
mod server_delete;
mod server_edit;
mod server_fetch;
mod channel_create;
mod member_edit;
mod member_fetch;
mod member_fetch_all;
mod member_remove;
mod ban_create;
mod ban_list;
mod ban_remove;
mod invites_fetch;
mod roles_create;
mod roles_edit;
mod roles_delete;
mod permissions_set;
mod permissions_set_default;
pub fn routes() -> Vec<Route> {
routes![
server_create::req,
server_delete::req,
server_fetch::req,
server_edit::req,
channel_create::req,
member_fetch_all::req,
member_remove::req,
member_fetch::req,
member_edit::req,
ban_create::req,
ban_remove::req,
ban_list::req,
invites_fetch::req,
roles_create::req,
roles_edit::req,
roles_delete::req,
permissions_set::req,
permissions_set_default::req
]
}
use mongodb::bson::doc;
use rocket_contrib::json::Json;
use serde::{Serialize, Deserialize};
use crate::database::*;
use crate::database::permissions::channel::ChannelPermission;
use crate::database::permissions::server::ServerPermission;
use crate::notifications::events::ClientboundNotification;
use crate::util::result::{Error, Result};
#[derive(Serialize, Deserialize)]
pub struct Values {
server: u32,
channel: u32
}
#[derive(Serialize, Deserialize)]
pub struct Data {
permissions: Values
}
#[put("/<target>/permissions/<role_id>", data = "<data>", rank = 2)]
pub async fn req(user: User, target: Ref, role_id: String, data: Json<Data>) -> Result<()> {
let target = target.fetch_server().await?;
let perm = permissions::PermissionCalculator::new(&user)
.with_server(&target)
.for_server()
.await?;
if !perm.get_manage_roles() {
return Err(Error::MissingPermission);
}
if !target.roles.contains_key(&role_id) {
return Err(Error::NotFound);
}
let server_permissions: u32 = ServerPermission::View as u32 | data.permissions.server;
let channel_permissions: u32 = ChannelPermission::View as u32 | data.permissions.channel;
get_collection("servers")
.update_one(
doc! { "_id": &target.id },
doc! {
"$set": {
"roles.".to_owned() + &role_id + &".permissions": [
server_permissions as i32,
channel_permissions as i32
]
}
},
None
)
.await
.map_err(|_| Error::DatabaseError {
operation: "update_one",
with: "server"
})?;
ClientboundNotification::ServerRoleUpdate {
id: target.id.clone(),
role_id,
data: json!({
"permissions": [
server_permissions as i32,
channel_permissions as i32
]
}),
clear: None
}
.publish(target.id);
Ok(())
}
use mongodb::bson::doc;
use rocket_contrib::json::Json;
use serde::{Serialize, Deserialize};
use crate::database::*;
use crate::database::permissions::channel::ChannelPermission;
use crate::database::permissions::server::ServerPermission;
use crate::notifications::events::ClientboundNotification;
use crate::util::result::{Error, Result};
#[derive(Serialize, Deserialize)]
pub struct Values {
server: u32,
channel: u32
}
#[derive(Serialize, Deserialize)]
pub struct Data {
permissions: Values
}
#[put("/<target>/permissions/default", data = "<data>", rank = 1)]
pub async fn req(user: User, target: Ref, data: Json<Data>) -> Result<()> {
let target = target.fetch_server().await?;
let perm = permissions::PermissionCalculator::new(&user)
.with_server(&target)
.for_server()
.await?;
if !perm.get_manage_roles() {
return Err(Error::MissingPermission);
}
let server_permissions: u32 = ServerPermission::View as u32 | data.permissions.server;
let channel_permissions: u32 = ChannelPermission::View as u32 | data.permissions.channel;
get_collection("servers")
.update_one(
doc! { "_id": &target.id },
doc! {
"$set": {
"default_permissions": [
server_permissions as i32,
channel_permissions as i32
]
}
},
None
)
.await
.map_err(|_| Error::DatabaseError {
operation: "update_one",
with: "server"
})?;
ClientboundNotification::ServerUpdate {
id: target.id.clone(),
data: json!({
"default_permissions": [
server_permissions as i32,
channel_permissions as i32
]
}),
clear: None
}
.publish(target.id);
Ok(())
}
use crate::database::*;
use crate::notifications::events::ClientboundNotification;
use crate::util::result::{Error, Result};
use ulid::Ulid;
use mongodb::bson::doc;
use validator::Validate;
use serde::{Serialize, Deserialize};
use rocket_contrib::json::{Json, JsonValue};
#[derive(Validate, Serialize, Deserialize)]
pub struct Data {
#[validate(length(min = 1, max = 32))]
name: String
}
#[post("/<target>/roles", data = "<data>")]
pub async fn req(user: User, target: Ref, data: Json<Data>) -> Result<JsonValue> {
let data = data.into_inner();
data.validate()
.map_err(|error| Error::FailedValidation { error })?;
let target = target.fetch_server().await?;
let perm = permissions::PermissionCalculator::new(&user)
.with_server(&target)
.for_server()
.await?;
if !perm.get_manage_roles() {
Err(Error::MissingPermission)?
}
let id = Ulid::new().to_string();
let perm_tuple = (
*permissions::server::DEFAULT_PERMISSION as i32,
*permissions::channel::DEFAULT_PERMISSION_SERVER as i32
);
get_collection("servers")
.update_one(
doc! {
"_id": &target.id
},
doc! {
"$set": {
"roles.".to_owned() + &id: {
"name": &data.name,
"permissions": [
&perm_tuple.0,
&perm_tuple.1
]
}
}
},
None
)
.await
.map_err(|_| Error::DatabaseError {
operation: "update_one",
with: "servers"
})?;
ClientboundNotification::ServerRoleUpdate {
id: target.id.clone(),
role_id: id.clone(),
data: json!({
"name": data.name,
"permissions": &perm_tuple
}),
clear: None
}
.publish(target.id);
Ok(json!({ "id": id, "permissions": perm_tuple }))
}
use crate::database::*;
use crate::notifications::events::ClientboundNotification;
use crate::util::result::{Error, Result};
use mongodb::bson::doc;
#[delete("/<target>/roles/<role_id>")]
pub async fn req(user: User, target: Ref, role_id: String) -> Result<()> {
let target = target.fetch_server().await?;
let perm = permissions::PermissionCalculator::new(&user)
.with_server(&target)
.for_server()
.await?;
if !perm.get_manage_roles() {
Err(Error::MissingPermission)?
}
get_collection("servers")
.update_one(
doc! {
"_id": &target.id
},
doc! {
"$unset": {
"roles.".to_owned() + &role_id: 1
}
},
None
)
.await
.map_err(|_| Error::DatabaseError {
operation: "update_one",
with: "servers"
})?;
get_collection("channels")
.update_one(
doc! {
"server": &target.id
},
doc! {
"$unset": {
"role_permissions.".to_owned() + &role_id: 1
}
},
None
)
.await
.map_err(|_| Error::DatabaseError {
operation: "update_one",
with: "channels"
})?;
get_collection("server_members")
.update_many(
doc! {
"_id.server": &target.id
},
doc! {
"$pull": {
"roles": &role_id
}
},
None
)
.await
.map_err(|_| Error::DatabaseError {
operation: "update_many",
with: "server_members"
})?;
ClientboundNotification::ServerRoleDelete {
id: target.id.clone(),
role_id
}
.publish(target.id);
Ok(())
}
use crate::notifications::events::ClientboundNotification;
use crate::util::result::{Error, Result};
use crate::{database::*, notifications::events::RemoveRoleField};
use mongodb::bson::doc;
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 = 1, max = 32))]
colour: Option<String>,
remove: Option<RemoveRoleField>,
}
#[patch("/<target>/roles/<role_id>", data = "<data>")]
pub async fn req(user: User, target: Ref, role_id: String, data: Json<Data>) -> Result<()> {
let data = data.into_inner();
data.validate()
.map_err(|error| Error::FailedValidation { error })?;
if data.name.is_none() && data.colour.is_none() && data.remove.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_roles() {
return Err(Error::MissingPermission)
}
if !target.roles.contains_key(&role_id) {
return Err(Error::InvalidRole)
}
let mut set = doc! {};
let mut unset = doc! {};
// ! FIXME: we should probably just require clients to support basic MQL incl. $set / $unset
let mut set_update = doc! {};
let role_key = "roles.".to_owned() + &role_id;
if let Some(remove) = &data.remove {
match remove {
RemoveRoleField::Colour => {
unset.insert(role_key.clone() + ".colour", 1);
}
}
}
if let Some(name) = &data.name {
set.insert(role_key.clone() + ".name", name);
set_update.insert("name", name);
}
if let Some(colour) = &data.colour {
set.insert(role_key.clone() + ".colour", colour);
set_update.insert("colour", colour);
}
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::ServerRoleUpdate {
id: target.id.clone(),
role_id,
data: json!(set_update),
clear: data.remove,
}
.publish(target.id.clone());
Ok(())
}
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;
mod get_settings;
mod get_unreads;
mod set_settings;
pub fn routes() -> Vec<Route> {
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(())
}
use crate::database::{ self, user::User, channel::Channel };
use crate::routes::channel;
use rocket_contrib::json::{ Json, JsonValue };
use serde::{ Serialize, Deserialize };
use bson::{ bson, doc, from_bson };
use mongodb::options::FindOptions;
use ulid::Ulid;
/// retrieve your user information
#[get("/@me")]
pub fn me(user: User) -> JsonValue {
json!({
"id": user.id,
"username": user.username,
"email": user.email,
"verified": user.email_verification.verified,
"created_timestamp": Ulid::from_string(&user.id).unwrap().datetime().timestamp(),
})
}
/// retrieve another user's information
#[get("/<_target>")]
pub fn user(_user: User, _target: User) -> JsonValue {
json!([])
}
#[derive(Serialize, Deserialize)]
pub struct Query {
username: String,
}
/// lookup a user on Revolt
/// currently only supports exact username searches
#[post("/lookup", data = "<query>")]
pub fn lookup(_user: User, query: Json<Query>) -> JsonValue {
let col = database::get_collection("users");
let users = col.find(
doc! { "username": query.username.clone() },
FindOptions::builder().limit(10).build()
).expect("Failed user lookup");
let mut results = Vec::new();
for user in users {
let u: User = from_bson(bson::Bson::Document(user.unwrap())).expect("Failed to unwrap user.");
results.push(
json!({
"id": u.id,
"username": u.username
})
);
}
json!(results)
}
/// retrieve all of your DMs
#[get("/@me/dms")]
pub fn dms(user: User) -> JsonValue {
let col = database::get_collection("channels");
let results = col.find(
doc! {
"$or": [
{
"type": channel::ChannelType::DM as i32
},
{
"type": channel::ChannelType::GROUPDM as i32
}
],
"recipients": user.id
},
None
).expect("Failed channel lookup");
let mut channels = Vec::new();
for item in results {
let channel: Channel = from_bson(bson::Bson::Document(item.unwrap())).expect("Failed to unwrap channel.");
channels.push(
json!({
"id": channel.id,
"type": channel.channel_type,
"recipients": channel.recipients,
"active": channel.active.unwrap()
})
);
}
json!(channels)
}
/// open a DM with a user
#[get("/<target>/dm")]
pub fn dm(user: User, target: User) -> JsonValue {
let col = database::get_collection("channels");
match col.find_one(
doc! { "type": channel::ChannelType::DM as i32, "recipients": [ user.id.clone(), target.id.clone() ] },
None
).expect("Failed channel lookup") {
Some(channel) =>
json!({
"id": channel.get_str("_id").unwrap()
}),
None => {
let id = Ulid::new();
col.insert_one(
doc! {
"_id": id.to_string(),
"type": channel::ChannelType::DM as i32,
"recipients": [ user.id, target.id ],
"active": false
},
None
).expect("Failed insert query.");
json!({
"id": id.to_string()
})
}
}
}
enum Relationship {
FRIEND = 0,
OUTGOING = 1,
INCOMING = 2,
BLOCKED = 3,
BLOCKEDOTHER = 4,
NONE = 5,
SELF = 6,
}
fn get_relationship(a: &User, b: &User) -> Relationship {
if a.id == b.id {
return Relationship::SELF
}
if let Some(arr) = &b.relations {
for entry in arr {
if entry.id == a.id {
match entry.status {
0 => {
return Relationship::FRIEND
},
1 => {
return Relationship::INCOMING
},
2 => {
return Relationship::OUTGOING
},
3 => {
return Relationship::BLOCKEDOTHER
},
4 => {
return Relationship::BLOCKED
},
_ => {
return Relationship::NONE
}
}
}
}
}
Relationship::NONE
}
/// retrieve all of your friends
#[get("/@me/friend")]
pub fn get_friends(user: User) -> JsonValue {
let mut results = Vec::new();
if let Some(arr) = user.relations {
for item in arr {
results.push(
json!({
"id": item.id,
"status": item.status
})
)
}
}
json!(results)
}
/// retrieve friend status with user
#[get("/<target>/friend")]
pub fn get_friend(user: User, target: User) -> JsonValue {
let relationship = get_relationship(&user, &target);
json!({
"id": target.id,
"status": relationship as u8
})
}
/// create or accept a friend request
#[put("/<target>/friend")]
pub fn add_friend(user: User, target: User) -> JsonValue {
let col = database::get_collection("users");
let relationship = get_relationship(&user, &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": user.id.clone(),
"relations.id": target.id.clone()
},
doc! {
"$set": {
"relations.$.status": Relationship::FRIEND as i32
}
},
None
).expect("Failed update query.");
col.update_one(
doc! {
"_id": target.id,
"relations.id": user.id
},
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::BLOCKEDOTHER =>
json!({
"success": false,
"error": "You have been blocked by this person."
}),
Relationship::NONE => {
col.update_one(
doc! {
"_id": user.id.clone()
},
doc! {
"$push": {
"relations": {
"id": target.id.clone(),
"status": Relationship::OUTGOING as i32
}
}
},
None
).expect("Failed update query.");
col.update_one(
doc! {
"_id": target.id
},
doc! {
"$push": {
"relations": {
"id": user.id,
"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("/<target>/friend")]
pub fn remove_friend(user: User, target: User) -> JsonValue {
let col = database::get_collection("users");
let relationship = get_relationship(&user, &target);
match relationship {
Relationship::FRIEND |
Relationship::OUTGOING |
Relationship::INCOMING => {
col.update_one(
doc! {
"_id": user.id.clone()
},
doc! {
"$pull": {
"relations": {
"id": target.id.clone()
}
}
},
None
).expect("Failed update query.");
col.update_one(
doc! {
"_id": target.id
},
doc! {
"$pull": {
"relations": {
"id": user.id
}
}
},
None
).expect("Failed update query.");
json!({
"success": true
})
},
Relationship::BLOCKED |
Relationship::BLOCKEDOTHER |
Relationship::NONE |
Relationship::SELF =>
json!({
"success": false,
"error": "This has no effect."
})
}
}
use crate::database::*;
use crate::notifications::events::ClientboundNotification;
use crate::util::result::{Error, Result};
use futures::try_join;
use mongodb::bson::doc;
use mongodb::options::{Collation, FindOneOptions};
use rocket_contrib::json::JsonValue;
#[put("/<username>/friend")]
pub async fn req(user: User, username: String) -> Result<JsonValue> {
let col = get_collection("users");
let doc = col
.find_one(
doc! {
"username": username
},
FindOneOptions::builder()
.collation(Collation::builder().locale("en").strength(2).build())
.build(),
)
.await
.map_err(|_| Error::DatabaseError {
operation: "find_one",
with: "user",
})?
.ok_or_else(|| Error::UnknownUser)?;
let target_id = doc.get_str("_id").map_err(|_| Error::DatabaseError {
operation: "get_str(_id)",
with: "user",
})?;
let target_user = Ref::from(target_id.to_string())?.fetch_user().await?;
match get_relationship(&user, &target_id) {
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(_) => {
let target_user = target_user
.from_override(&user, RelationshipStatus::Friend)
.await?;
let user = user
.from_override(&target_user, RelationshipStatus::Friend)
.await?;
ClientboundNotification::UserRelationship {
id: user.id.clone(),
user: target_user,
status: RelationshipStatus::Friend,
}
.publish(user.id.clone());
ClientboundNotification::UserRelationship {
id: target_id.to_string(),
user,
status: RelationshipStatus::Friend,
}
.publish(target_id.to_string());
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(_) => {
let target_user = target_user
.from_override(&user, RelationshipStatus::Outgoing)
.await?;
let user = user
.from_override(&target_user, RelationshipStatus::Incoming)
.await?;
ClientboundNotification::UserRelationship {
id: user.id.clone(),
user: target_user,
status: RelationshipStatus::Outgoing,
}
.publish(user.id.clone());
ClientboundNotification::UserRelationship {
id: target_id.to_string(),
user,
status: RelationshipStatus::Incoming,
}
.publish(target_id.to_string());
Ok(json!({ "status": "Outgoing" }))
}
Err(_) => Err(Error::DatabaseError {
operation: "update_one",
with: "user",
}),
}
}
}
}
use crate::database::*;
use crate::notifications::events::ClientboundNotification;
use crate::util::result::{Error, Result};
use futures::try_join;
use mongodb::bson::doc;
use rocket_contrib::json::JsonValue;
#[put("/<target>/block")]
pub async fn req(user: User, target: Ref) -> Result<JsonValue> {
let col = get_collection("users");
let target = target.fetch_user().await?;
match get_relationship(&user, &target.id) {
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",
})?;
ClientboundNotification::UserRelationship {
id: user.id.clone(),
user: target,
status: RelationshipStatus::Blocked,
}
.publish(user.id.clone());
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(_) => {
let target = target
.from_override(&user, RelationshipStatus::Blocked)
.await?;
let user = user
.from_override(&target, RelationshipStatus::BlockedOther)
.await?;
let target_id = target.id.clone();
ClientboundNotification::UserRelationship {
id: user.id.clone(),
user: target,
status: RelationshipStatus::Blocked,
}
.publish(user.id.clone());
ClientboundNotification::UserRelationship {
id: target_id.clone(),
user,
status: RelationshipStatus::BlockedOther,
}
.publish(target_id);
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(_) => {
let target = target
.from_override(&user, RelationshipStatus::Blocked)
.await?;
let user = user
.from_override(&target, RelationshipStatus::BlockedOther)
.await?;
let target_id = target.id.clone();
ClientboundNotification::UserRelationship {
id: user.id.clone(),
user: target,
status: RelationshipStatus::Blocked,
}
.publish(user.id.clone());
ClientboundNotification::UserRelationship {
id: target_id.clone(),
user,
status: RelationshipStatus::BlockedOther,
}
.publish(target_id);
Ok(json!({ "status": "Blocked" }))
}
Err(_) => Err(Error::DatabaseError {
operation: "update_one",
with: "user",
}),
}
}
}
}
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(())
}