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 1289 additions and 348 deletions
use std::collections::HashSet;
use crate::notifications::events::ClientboundNotification;
use crate::util::result::{Error, Result};
use crate::{database::*, notifications::events::RemoveMemberField};
use mongodb::bson::{doc, 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))]
nickname: Option<String>,
avatar: Option<String>,
roles: Option<Vec<String>>,
remove: Option<RemoveMemberField>,
}
#[patch("/<server>/members/<target>", data = "<data>")]
pub async fn req(user: User, server: Ref, target: String, data: Json<Data>) -> Result<()> {
let data = data.into_inner();
data.validate()
.map_err(|error| Error::FailedValidation { error })?;
if data.nickname.is_none() && data.avatar.is_none() && data.roles.is_none() && data.remove.is_none() {
return Ok(());
}
let server = server.fetch_server().await?;
let target = Ref::from(target)?.fetch_member(&server.id).await?;
let perm = permissions::PermissionCalculator::new(&user)
.with_server(&server)
.for_server()
.await?;
if data.roles.is_some() && !perm.get_manage_roles() {
return Err(Error::MissingPermission);
}
if target.id.user == user.id {
if (data.nickname.is_some() && !perm.get_change_nickname())
|| (data.avatar.is_some() && !perm.get_change_avatar())
{
return Err(Error::MissingPermission);
}
if let Some(remove) = &data.remove {
if match remove {
RemoveMemberField::Avatar => !perm.get_change_avatar(),
RemoveMemberField::Nickname => !perm.get_change_nickname(),
} {
return Err(Error::MissingPermission);
}
}
} else {
if data.avatar.is_some() || (data.nickname.is_some() && !perm.get_manage_nicknames()) {
return Err(Error::MissingPermission);
}
if let Some(remove) = &data.remove {
if match remove {
RemoveMemberField::Avatar => !perm.get_remove_avatars(),
RemoveMemberField::Nickname => !perm.get_manage_nicknames(),
} {
return Err(Error::MissingPermission);
}
}
}
let mut set = doc! {};
let mut unset = doc! {};
let mut remove_avatar = false;
if let Some(remove) = &data.remove {
match remove {
RemoveMemberField::Avatar => {
unset.insert("avatar", 1);
remove_avatar = true;
}
RemoveMemberField::Nickname => {
unset.insert("nickname", 1);
}
}
}
if let Some(name) = &data.nickname {
set.insert("nickname", name);
}
if let Some(attachment_id) = &data.avatar {
let attachment =
File::find_and_use(&attachment_id, "avatars", "user", &target.id.user).await?;
set.insert(
"avatar",
to_document(&attachment).map_err(|_| Error::DatabaseError {
operation: "to_document",
with: "attachment",
})?,
);
remove_avatar = true;
}
if let Some(role_ids) = &data.roles {
let mut ids = HashSet::new();
for role in role_ids {
if server.roles.contains_key(role) {
ids.insert(role.clone());
}
}
set.insert("roles", ids.into_iter().collect::<Vec<String>>());
}
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("server_members")
.update_one(
doc! { "_id.server": &server.id, "_id.user": &target.id.user },
operations,
None,
)
.await
.map_err(|_| Error::DatabaseError {
operation: "update_one",
with: "server_member",
})?;
}
ClientboundNotification::ServerMemberUpdate {
id: target.id.clone(),
data: json!(set),
clear: data.remove,
}
.publish(server.id.clone());
let Member { avatar, .. } = target;
if remove_avatar {
if let Some(old_avatar) = avatar {
old_avatar.delete().await?;
}
}
Ok(())
}
use crate::database::*;
use crate::util::result::{Error, Result};
use mongodb::bson::doc;
use rocket_contrib::json::JsonValue;
#[get("/<target>/members/<member>")]
pub async fn req(user: User, target: Ref, member: String) -> 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!(Ref::from(member)?.fetch_member(&target.id).await?))
}
use crate::database::*;
use crate::util::result::{Error, Result};
use futures::StreamExt;
use mongodb::bson::{doc, from_document, Document};
use rocket_contrib::json::JsonValue;
// ! FIXME: this is a temporary route while permissions are being worked on.
#[get("/<target>/members")]
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)?
}
let members = get_collection("server_members")
.find(
doc! {
"_id.server": target.id
},
None,
)
.await
.map_err(|_| Error::DatabaseError {
operation: "find",
with: "server_members",
})?
.filter_map(async move |s| s.ok())
.collect::<Vec<Document>>()
.await
.into_iter()
.filter_map(|x| from_document(x).ok())
.collect::<Vec<Member>>();
let member_ids = members
.iter()
.map(|m| m.id.user.clone())
.collect::<Vec<String>>();
Ok(json!({
"members": members,
"users": user.fetch_multiple_users(member_ids).await?
}))
}
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, channel::Channel, user::User};
use crate::routes::channel;
use bson::{bson, doc, from_bson};
use mongodb::options::FindOptions;
use rocket_contrib::json::{Json, JsonValue};
use serde::{Deserialize, Serialize};
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,
})
}
/// retrieve another user's information
#[get("/<target>")]
pub fn user(user: User, target: User) -> JsonValue {
json!({
"id": target.id,
"username": target.username,
"relationship": get_relationship(&user, &target) as u8
})
}
#[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 item in users {
let u: User =
from_bson(bson::Bson::Document(item.unwrap())).expect("Failed to unwrap user.");
results.push(json!({
"id": u.id,
"username": u.username,
"relationship": get_relationship(&user, &u) as u8
}));
}
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": { "$all": [ user.id.clone(), target.id.clone() ] } },
None
).expect("Failed channel lookup") {
Some(channel) =>
json!({
"success": true,
"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!({
"success": true,
"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,
"status": Relationship::FRIEND as u8,
})
}
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,
"status": Relationship::OUTGOING as u8,
})
}
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",
}),
}
}
}
}