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 874 additions and 211 deletions
...@@ -11,6 +11,7 @@ pub async fn req(user: User, target: Ref) -> Result<JsonValue> { ...@@ -11,6 +11,7 @@ pub async fn req(user: User, target: Ref) -> Result<JsonValue> {
.with_channel(&target) .with_channel(&target)
.for_channel() .for_channel()
.await?; .await?;
if !perm.get_view() { if !perm.get_view() {
Err(Error::MissingPermission)? Err(Error::MissingPermission)?
} }
......
use crate::database::*;
use crate::util::result::{Error, Result};
use crate::notifications::events::ClientboundNotification;
use validator::Validate;
use rocket_contrib::json::Json;
use serde::{Serialize, Deserialize};
use mongodb::bson::{doc, to_document};
#[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>,
}
#[patch("/<target>", data = "<info>")]
pub async fn req(user: User, target: Ref, info: Json<Data>) -> Result<()> {
info.validate()
.map_err(|error| Error::FailedValidation { error })?;
if info.name.is_none() && info.description.is_none() {
return Ok(())
}
let target = target.fetch_channel().await?;
let perm = permissions::PermissionCalculator::new(&user)
.with_channel(&target)
.for_channel()
.await?;
if !perm.get_manage_channel() {
Err(Error::MissingPermission)?
}
match &target {
Channel::Group { id, .. } => {
let col = get_collection("channels");
let mut set = doc! {};
if let Some(name) = &info.name {
set.insert("name", name.clone());
}
if let Some(description) = &info.description {
set.insert("description", description.clone());
}
col.update_one(
doc! { "_id": &id },
doc! { "$set": set },
None
)
.await
.map_err(|_| Error::DatabaseError { operation: "update_one", with: "channel" })?;
ClientboundNotification::ChannelUpdate {
id: id.clone(),
data: json!(info.0)
}
.publish(id.clone())
.await
.ok();
if let Some(name) = &info.name {
Message::create(
"00000000000000000000000000".to_string(),
id.clone(),
Content::SystemMessage(SystemMessage::ChannelRenamed { name: name.clone(), by: user.id })
)
.publish(&target)
.await
.ok();
}
Ok(())
}
_ => Err(Error::InvalidOperation)
}
}
...@@ -15,7 +15,8 @@ pub async fn req(user: User, target: Ref, member: Ref) -> Result<()> { ...@@ -15,7 +15,8 @@ pub async fn req(user: User, target: Ref, member: Ref) -> Result<()> {
.with_channel(&channel) .with_channel(&channel)
.for_channel() .for_channel()
.await?; .await?;
if !perm.get_view() {
if !perm.get_invite_others() {
Err(Error::MissingPermission)? Err(Error::MissingPermission)?
} }
...@@ -52,16 +53,13 @@ pub async fn req(user: User, target: Ref, member: Ref) -> Result<()> { ...@@ -52,16 +53,13 @@ pub async fn req(user: User, target: Ref, member: Ref) -> Result<()> {
id: id.clone(), id: id.clone(),
user: member.id.clone(), user: member.id.clone(),
} }
.publish(id.clone()) .publish(id.clone());
.await
.ok();
Message::create( Content::SystemMessage(SystemMessage::UserAdded {
"00000000000000000000000000".to_string(), id: member.id,
id.clone(), by: user.id,
Content::SystemMessage(SystemMessage::UserAdded { id: member.id, by: user.id }) })
) .send_as_system(&channel)
.publish(&channel)
.await .await
.ok(); .ok();
......
...@@ -24,6 +24,7 @@ pub struct Data { ...@@ -24,6 +24,7 @@ pub struct Data {
#[post("/create", data = "<info>")] #[post("/create", data = "<info>")]
pub async fn req(user: User, info: Json<Data>) -> Result<JsonValue> { pub async fn req(user: User, info: Json<Data>) -> Result<JsonValue> {
let info = info.into_inner();
info.validate() info.validate()
.map_err(|error| Error::FailedValidation { error })?; .map_err(|error| Error::FailedValidation { error })?;
...@@ -37,8 +38,11 @@ pub async fn req(user: User, info: Json<Data>) -> Result<JsonValue> { ...@@ -37,8 +38,11 @@ pub async fn req(user: User, info: Json<Data>) -> Result<JsonValue> {
} }
for target in &set { for target in &set {
if get_relationship(&user, target) != RelationshipStatus::Friend { match get_relationship(&user, target) {
Err(Error::NotFriends)? RelationshipStatus::Friend | RelationshipStatus::User => {}
_ => {
return Err(Error::NotFriends);
}
} }
} }
...@@ -62,15 +66,14 @@ pub async fn req(user: User, info: Json<Data>) -> Result<JsonValue> { ...@@ -62,15 +66,14 @@ pub async fn req(user: User, info: Json<Data>) -> Result<JsonValue> {
let id = Ulid::new().to_string(); let id = Ulid::new().to_string();
let channel = Channel::Group { let channel = Channel::Group {
id, id,
nonce: Some(info.nonce.clone()), nonce: Some(info.nonce),
name: info.name.clone(), name: info.name,
description: info description: info.description,
.description
.clone()
.unwrap_or_else(|| "A group.".to_string()),
owner: user.id, owner: user.id,
recipients: set.into_iter().collect::<Vec<String>>(), recipients: set.into_iter().collect::<Vec<String>>(),
icon: None,
last_message: None, last_message: None,
permissions: None
}; };
channel.clone().publish().await?; channel.clone().publish().await?;
......
...@@ -49,16 +49,13 @@ pub async fn req(user: User, target: Ref, member: Ref) -> Result<()> { ...@@ -49,16 +49,13 @@ pub async fn req(user: User, target: Ref, member: Ref) -> Result<()> {
id: id.clone(), id: id.clone(),
user: member.id.clone(), user: member.id.clone(),
} }
.publish(id.clone()) .publish(id.clone());
.await
.ok();
Message::create( Content::SystemMessage(SystemMessage::UserRemove {
"00000000000000000000000000".to_string(), id: member.id,
id.clone(), by: user.id,
Content::SystemMessage(SystemMessage::UserRemove { id: member.id, by: user.id }) })
) .send_as_system(&channel)
.publish(&channel)
.await .await
.ok(); .ok();
......
use crate::database::*;
use crate::util::result::{Error, Result};
use mongodb::bson::doc;
use nanoid::nanoid;
use rocket_contrib::json::JsonValue;
lazy_static! {
static ref ALPHABET: [char; 54] = [
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
'J', 'K', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd',
'e', 'f', 'g', 'h', 'j', 'k', 'm', 'n', 'p', 'q', 'r', 's', 't', 'v', 'w', 'x', 'y', 'z'
];
}
#[post("/<target>/invites")]
pub async fn req(user: User, target: Ref) -> Result<JsonValue> {
let target = target.fetch_channel().await?;
let perm = permissions::PermissionCalculator::new(&user)
.with_channel(&target)
.for_channel()
.await?;
if !perm.get_invite_others() {
return Err(Error::MissingPermission);
}
let code = nanoid!(8, &*ALPHABET);
match &target {
Channel::Group { .. } => {
unimplemented!()
}
Channel::TextChannel { id, server, .. }
| Channel::VoiceChannel { id, server, .. } => {
Invite::Server {
code: code.clone(),
creator: user.id,
server: server.clone(),
channel: id.clone(),
}
.save()
.await?;
Ok(json!({ "code": code }))
}
_ => Err(Error::InvalidOperation),
}
}
use crate::database::*;
use crate::util::result::{Error, Result};
use rocket_contrib::json::JsonValue;
#[get("/<target>/members")]
pub async fn req(user: User, target: Ref) -> Result<JsonValue> {
let target = target.fetch_channel().await?;
let perm = permissions::PermissionCalculator::new(&user)
.with_channel(&target)
.for_channel()
.await?;
if !perm.get_view() {
Err(Error::MissingPermission)?
}
if let Channel::Group { recipients, .. } = target {
Ok(json!(user.fetch_multiple_users(recipients).await?))
} else {
Err(Error::InvalidOperation)
}
}
...@@ -6,6 +6,7 @@ use mongodb::bson::doc; ...@@ -6,6 +6,7 @@ use mongodb::bson::doc;
#[delete("/<target>/messages/<msg>")] #[delete("/<target>/messages/<msg>")]
pub async fn req(user: User, target: Ref, msg: Ref) -> Result<()> { pub async fn req(user: User, target: Ref, msg: Ref) -> Result<()> {
let channel = target.fetch_channel().await?; let channel = target.fetch_channel().await?;
channel.has_messaging()?;
let perm = permissions::PermissionCalculator::new(&user) let perm = permissions::PermissionCalculator::new(&user)
.with_channel(&channel) .with_channel(&channel)
......
...@@ -2,7 +2,7 @@ use crate::database::*; ...@@ -2,7 +2,7 @@ use crate::database::*;
use crate::util::result::{Error, Result}; use crate::util::result::{Error, Result};
use chrono::Utc; use chrono::Utc;
use mongodb::bson::{doc, Bson, DateTime}; use mongodb::bson::{doc, Bson, DateTime, Document};
use rocket_contrib::json::Json; use rocket_contrib::json::Json;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use validator::Validate; use validator::Validate;
...@@ -19,6 +19,8 @@ pub async fn req(user: User, target: Ref, msg: Ref, edit: Json<Data>) -> Result< ...@@ -19,6 +19,8 @@ pub async fn req(user: User, target: Ref, msg: Ref, edit: Json<Data>) -> Result<
.map_err(|error| Error::FailedValidation { error })?; .map_err(|error| Error::FailedValidation { error })?;
let channel = target.fetch_channel().await?; let channel = target.fetch_channel().await?;
channel.has_messaging()?;
let perm = permissions::PermissionCalculator::new(&user) let perm = permissions::PermissionCalculator::new(&user)
.with_channel(&channel) .with_channel(&channel)
.for_channel() .for_channel()
...@@ -27,22 +29,41 @@ pub async fn req(user: User, target: Ref, msg: Ref, edit: Json<Data>) -> Result< ...@@ -27,22 +29,41 @@ pub async fn req(user: User, target: Ref, msg: Ref, edit: Json<Data>) -> Result<
Err(Error::MissingPermission)? Err(Error::MissingPermission)?
} }
let message = msg.fetch_message(&channel).await?; let mut message = msg.fetch_message(&channel).await?;
if message.author != user.id { if message.author != user.id {
Err(Error::CannotEditMessage)? Err(Error::CannotEditMessage)?
} }
let edited = Utc::now(); let edited = Utc::now();
let mut set = doc! {
"content": &edit.content,
"edited": Bson::DateTime(edited)
};
message.content = Content::Text(edit.content.clone());
let mut update = json!({ "content": edit.content, "edited": DateTime(edited) });
if let Some(embeds) = &message.embeds {
let new_embeds: Vec<Document> = vec![];
for embed in embeds {
match embed {
Embed::Website(_) | Embed::Image(_) | Embed::None => {} // Otherwise push to new_embeds.
}
}
let obj = update.as_object_mut().unwrap();
obj.insert("embeds".to_string(), json!(new_embeds).0);
set.insert("embeds", new_embeds);
}
get_collection("messages") get_collection("messages")
.update_one( .update_one(
doc! { doc! {
"_id": &message.id "_id": &message.id
}, },
doc! { doc! {
"$set": { "$set": set
"content": &edit.content,
"edited": Bson::DateTime(edited)
}
}, },
None, None,
) )
...@@ -52,7 +73,5 @@ pub async fn req(user: User, target: Ref, msg: Ref, edit: Json<Data>) -> Result< ...@@ -52,7 +73,5 @@ pub async fn req(user: User, target: Ref, msg: Ref, edit: Json<Data>) -> Result<
with: "message", with: "message",
})?; })?;
message message.publish_update(update).await
.publish_update(json!({ "content": edit.content, "edited": DateTime(edited) }))
.await
} }
...@@ -6,6 +6,7 @@ use rocket_contrib::json::JsonValue; ...@@ -6,6 +6,7 @@ use rocket_contrib::json::JsonValue;
#[get("/<target>/messages/<msg>")] #[get("/<target>/messages/<msg>")]
pub async fn req(user: User, target: Ref, msg: Ref) -> Result<JsonValue> { pub async fn req(user: User, target: Ref, msg: Ref) -> Result<JsonValue> {
let channel = target.fetch_channel().await?; let channel = target.fetch_channel().await?;
channel.has_messaging()?;
let perm = permissions::PermissionCalculator::new(&user) let perm = permissions::PermissionCalculator::new(&user)
.with_channel(&channel) .with_channel(&channel)
......
use std::collections::HashSet;
use crate::database::*; use crate::database::*;
use crate::util::result::{Error, Result}; use crate::util::result::{Error, Result};
use futures::StreamExt; use futures::{StreamExt, try_join};
use mongodb::{ use mongodb::{
bson::{doc, from_document}, bson::{doc, from_document},
options::FindOptions, options::FindOptions,
...@@ -11,6 +13,12 @@ use rocket_contrib::json::JsonValue; ...@@ -11,6 +13,12 @@ use rocket_contrib::json::JsonValue;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use validator::Validate; use validator::Validate;
#[derive(Serialize, Deserialize, FromFormValue)]
pub enum Sort {
Latest,
Oldest,
}
#[derive(Validate, Serialize, Deserialize, FromForm)] #[derive(Validate, Serialize, Deserialize, FromForm)]
pub struct Options { pub struct Options {
#[validate(range(min = 1, max = 100))] #[validate(range(min = 1, max = 100))]
...@@ -19,6 +27,13 @@ pub struct Options { ...@@ -19,6 +27,13 @@ pub struct Options {
before: Option<String>, before: Option<String>,
#[validate(length(min = 26, max = 26))] #[validate(length(min = 26, max = 26))]
after: Option<String>, after: Option<String>,
sort: Option<Sort>,
// Specifying 'nearby' ignores 'before', 'after' and 'sort'.
// It will also take half of limit rounded as the limits to each side.
// It also fetches the message ID specified.
#[validate(length(min = 26, max = 26))]
nearby: Option<String>,
include_users: Option<bool>,
} }
#[get("/<target>/messages?<options..>")] #[get("/<target>/messages?<options..>")]
...@@ -28,6 +43,7 @@ pub async fn req(user: User, target: Ref, options: Form<Options>) -> Result<Json ...@@ -28,6 +43,7 @@ pub async fn req(user: User, target: Ref, options: Form<Options>) -> Result<Json
.map_err(|error| Error::FailedValidation { error })?; .map_err(|error| Error::FailedValidation { error })?;
let target = target.fetch_channel().await?; let target = target.fetch_channel().await?;
target.has_messaging()?;
let perm = permissions::PermissionCalculator::new(&user) let perm = permissions::PermissionCalculator::new(&user)
.with_channel(&target) .with_channel(&target)
...@@ -37,43 +53,135 @@ pub async fn req(user: User, target: Ref, options: Form<Options>) -> Result<Json ...@@ -37,43 +53,135 @@ pub async fn req(user: User, target: Ref, options: Form<Options>) -> Result<Json
Err(Error::MissingPermission)? Err(Error::MissingPermission)?
} }
let mut query = doc! { "channel": target.id() }; let mut messages = vec![];
if let Some(before) = &options.before {
query.insert("_id", doc! { "$lt": before });
}
if let Some(after) = &options.after {
query.insert("_id", doc! { "$gt": after });
}
let mut cursor = get_collection("messages") let collection = get_collection("messages");
.find( let limit = options.limit.unwrap_or(50);
query, let channel = target.id();
FindOptions::builder() if let Some(nearby) = &options.nearby {
.limit(options.limit.unwrap_or(50)) let mut cursors = try_join!(
.sort(doc! { collection.find(
"_id": -1 doc! {
}) "channel": channel,
.build(), "_id": {
"$gte": &nearby
}
},
FindOptions::builder()
.limit(limit / 2 + 1)
.sort(doc! {
"_id": 1
})
.build(),
),
collection.find(
doc! {
"channel": channel,
"_id": {
"$lt": &nearby
}
},
FindOptions::builder()
.limit(limit / 2)
.sort(doc! {
"_id": -1
})
.build(),
)
) )
.await
.map_err(|_| Error::DatabaseError { .map_err(|_| Error::DatabaseError {
operation: "find", operation: "find",
with: "messages", with: "messages",
})?; })?;
let mut messages = vec![]; while let Some(result) = cursors.0.next().await {
while let Some(result) = cursor.next().await { if let Ok(doc) = result {
if let Ok(doc) = result { messages.push(
messages.push( from_document::<Message>(doc).map_err(|_| Error::DatabaseError {
from_document::<Message>(doc).map_err(|_| Error::DatabaseError { operation: "from_document",
operation: "from_document", with: "message",
with: "message", })?,
})?, );
); }
}
while let Some(result) = cursors.1.next().await {
if let Ok(doc) = result {
messages.push(
from_document::<Message>(doc).map_err(|_| Error::DatabaseError {
operation: "from_document",
with: "message",
})?,
);
}
}
} else {
let mut query = doc! { "channel": target.id() };
if let Some(before) = &options.before {
query.insert("_id", doc! { "$lt": before });
}
if let Some(after) = &options.after {
query.insert("_id", doc! { "$gt": after });
}
let sort: i32 = if let Sort::Latest = options.sort.as_ref().unwrap_or_else(|| &Sort::Latest) {
-1
} else {
1
};
let mut cursor = collection
.find(
query,
FindOptions::builder()
.limit(limit)
.sort(doc! {
"_id": sort
})
.build(),
)
.await
.map_err(|_| Error::DatabaseError {
operation: "find",
with: "messages",
})?;
while let Some(result) = cursor.next().await {
if let Ok(doc) = result {
messages.push(
from_document::<Message>(doc).map_err(|_| Error::DatabaseError {
operation: "from_document",
with: "message",
})?,
);
}
} }
} }
Ok(json!(messages)) if options.include_users.unwrap_or_else(|| false) {
let mut ids = HashSet::new();
for message in &messages {
ids.insert(message.author.clone());
}
ids.remove(&user.id);
let user_ids = ids.into_iter().collect();
let users = user.fetch_multiple_users(user_ids).await?;
if let Channel::TextChannel { server, .. } = target {
Ok(json!({
"messages": messages,
"users": users,
"members": Server::fetch_members(&server).await?
}))
} else {
Ok(json!({
"messages": messages,
"users": users,
}))
}
} else {
Ok(json!(messages))
}
} }
...@@ -18,6 +18,7 @@ pub async fn req(user: User, target: Ref, data: Json<Options>) -> Result<JsonVal ...@@ -18,6 +18,7 @@ pub async fn req(user: User, target: Ref, data: Json<Options>) -> Result<JsonVal
} }
let target = target.fetch_channel().await?; let target = target.fetch_channel().await?;
target.has_messaging()?;
let perm = permissions::PermissionCalculator::new(&user) let perm = permissions::PermissionCalculator::new(&user)
.with_channel(&target) .with_channel(&target)
......
use std::collections::HashSet;
use crate::database::*;
use crate::util::result::{Error, Result};
use futures::StreamExt;
use mongodb::{
bson::{doc, from_document},
options::FindOptions,
};
use rocket_contrib::json::{Json, JsonValue};
use serde::{Deserialize, Serialize};
use validator::Validate;
#[derive(Serialize, Deserialize, FromFormValue)]
pub enum Sort {
Relevance,
Latest,
Oldest,
}
impl Default for Sort {
fn default() -> Sort {
Sort::Relevance
}
}
#[derive(Validate, Serialize, Deserialize, FromForm)]
pub struct Options {
#[validate(length(min = 1, max = 64))]
query: String,
#[validate(range(min = 1, max = 100))]
limit: Option<i64>,
#[validate(length(min = 26, max = 26))]
before: Option<String>,
#[validate(length(min = 26, max = 26))]
after: Option<String>,
#[serde(default = "Sort::default")]
sort: Sort,
include_users: Option<bool>,
}
#[post("/<target>/search", data = "<options>")]
pub async fn req(user: User, target: Ref, options: Json<Options>) -> Result<JsonValue> {
options
.validate()
.map_err(|error| Error::FailedValidation { error })?;
let target = target.fetch_channel().await?;
target.has_messaging()?;
let perm = permissions::PermissionCalculator::new(&user)
.with_channel(&target)
.for_channel()
.await?;
if !perm.get_view() {
Err(Error::MissingPermission)?
}
let mut messages = vec![];
let limit = options.limit.unwrap_or(50);
let mut filter = doc! {
"channel": target.id(),
"$text": {
"$search": &options.query
}
};
if let Some(doc) = match (&options.before, &options.after) {
(Some(before), Some(after)) => Some(doc! {
"lt": before,
"gt": after
}),
(Some(before), _) => Some(doc! {
"lt": before
}),
(_, Some(after)) => Some(doc! {
"gt": after
}),
_ => None
} {
filter.insert("_id", doc);
}
let mut cursor = get_collection("messages")
.find(
filter,
FindOptions::builder()
.projection(
if let Sort::Relevance = &options.sort {
doc! {
"score": {
"$meta": "textScore"
}
}
} else {
doc! {}
}
)
.limit(limit)
.sort(
match &options.sort {
Sort::Relevance => doc! {
"score": {
"$meta": "textScore"
}
},
Sort::Latest => doc! {
"_id": -1
},
Sort::Oldest => doc! {
"_id": 1
}
}
)
.build(),
)
.await
.map_err(|_| Error::DatabaseError {
operation: "find",
with: "messages",
})?;
while let Some(result) = cursor.next().await {
if let Ok(doc) = result {
messages.push(
from_document::<Message>(doc).map_err(|_| Error::DatabaseError {
operation: "from_document",
with: "message",
})?,
);
}
}
if options.include_users.unwrap_or_else(|| false) {
let mut ids = HashSet::new();
for message in &messages {
ids.insert(message.author.clone());
}
ids.remove(&user.id);
let user_ids = ids.into_iter().collect();
let users = user.fetch_multiple_users(user_ids).await?;
if let Channel::TextChannel { server, .. } = target {
Ok(json!({
"messages": messages,
"users": users,
"members": Server::fetch_members(&server).await?
}))
} else {
Ok(json!({
"messages": messages,
"users": users,
}))
}
} else {
Ok(json!(messages))
}
}
use std::collections::HashSet;
use crate::database::*; use crate::database::*;
use crate::util::result::{Error, Result}; use crate::util::result::{Error, Result};
use mongodb::{ use mongodb::{bson::doc, options::FindOneOptions};
bson::{doc, from_document}, use regex::Regex;
options::FindOneOptions,
};
use rocket_contrib::json::{Json, JsonValue}; use rocket_contrib::json::{Json, JsonValue};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use ulid::Ulid; use ulid::Ulid;
use validator::Validate; use validator::Validate;
#[derive(Serialize, Deserialize)]
pub struct Reply {
id: String,
mention: bool
}
#[derive(Validate, Serialize, Deserialize)] #[derive(Validate, Serialize, Deserialize)]
pub struct Data { pub struct Data {
#[validate(length(min = 0, max = 2000))] #[validate(length(min = 0, max = 2000))]
...@@ -18,27 +24,37 @@ pub struct Data { ...@@ -18,27 +24,37 @@ pub struct Data {
#[validate(length(min = 1, max = 36))] #[validate(length(min = 1, max = 36))]
nonce: String, nonce: String,
#[validate(length(min = 1, max = 128))] #[validate(length(min = 1, max = 128))]
attachment: Option<String>, attachments: Option<Vec<String>>,
replies: Option<Vec<Reply>>,
}
lazy_static! {
static ref RE_ULID: Regex = Regex::new(r"<@([0123456789ABCDEFGHJKMNPQRSTVWXYZ]{26})>").unwrap();
} }
#[post("/<target>/messages", data = "<message>")] #[post("/<target>/messages", data = "<message>")]
pub async fn req(user: User, target: Ref, message: Json<Data>) -> Result<JsonValue> { pub async fn req(user: User, target: Ref, message: Json<Data>) -> Result<JsonValue> {
let message = message.into_inner();
message message
.validate() .validate()
.map_err(|error| Error::FailedValidation { error })?; .map_err(|error| Error::FailedValidation { error })?;
if message.content.len() == 0 && message.attachment.is_none() { if message.content.len() == 0
&& (message.attachments.is_none() || message.attachments.as_ref().unwrap().len() == 0)
{
return Err(Error::EmptyMessage); return Err(Error::EmptyMessage);
} }
let target = target.fetch_channel().await?; let target = target.fetch_channel().await?;
target.has_messaging()?;
let perm = permissions::PermissionCalculator::new(&user) let perm = permissions::PermissionCalculator::new(&user)
.with_channel(&target) .with_channel(&target)
.for_channel() .for_channel()
.await?; .await?;
if !perm.get_send_message() { if !perm.get_send_message() {
Err(Error::MissingPermission)? return Err(Error::MissingPermission)
} }
if get_collection("messages") if get_collection("messages")
...@@ -61,54 +77,49 @@ pub async fn req(user: User, target: Ref, message: Json<Data>) -> Result<JsonVal ...@@ -61,54 +77,49 @@ pub async fn req(user: User, target: Ref, message: Json<Data>) -> Result<JsonVal
} }
let id = Ulid::new().to_string(); let id = Ulid::new().to_string();
let attachments = get_collection("attachments");
let attachment = if let Some(attachment_id) = &message.attachment {
if let Some(doc) = attachments
.find_one(
doc! {
"_id": attachment_id,
"message_id": {
"$exists": false
}
},
None,
)
.await
.map_err(|_| Error::DatabaseError {
operation: "find_one",
with: "attachment",
})?
{
let attachment = from_document::<File>(doc).map_err(|_| Error::DatabaseError {
operation: "from_document",
with: "attachment",
})?;
let mut mentions = HashSet::new();
if let Some(captures) = RE_ULID.captures_iter(&message.content).next() {
// ! FIXME: in the future, verify in group so we can send out push
mentions.insert(captures[1].to_string());
}
let mut replies = HashSet::new();
if let Some(entries) = message.replies {
// ! FIXME: move this to app config
if entries.len() >= 5 {
return Err(Error::TooManyReplies)
}
for Reply { id, mention } in entries {
let message = Ref::from_unchecked(id)
.fetch_message(&target)
.await?;
replies.insert(message.id);
if mention {
mentions.insert(message.author);
}
}
}
let mut attachments = vec![];
if let Some(ids) = &message.attachments {
if ids.len() > 0 && !perm.get_upload_files() {
return Err(Error::MissingPermission)
}
// ! FIXME: move this to app config
if ids.len() >= 5 {
return Err(Error::TooManyAttachments)
}
for attachment_id in ids {
attachments attachments
.update_one( .push(File::find_and_use(attachment_id, "attachments", "message", &id).await?);
doc! {
"_id": &attachment.id
},
doc! {
"$set": {
"message_id": &id
}
},
None,
)
.await
.map_err(|_| Error::DatabaseError {
operation: "update_one",
with: "attachment",
})?;
Some(attachment)
} else {
return Err(Error::UnknownAttachment);
} }
} else { }
None
};
let msg = Message { let msg = Message {
id, id,
...@@ -116,13 +127,24 @@ pub async fn req(user: User, target: Ref, message: Json<Data>) -> Result<JsonVal ...@@ -116,13 +127,24 @@ pub async fn req(user: User, target: Ref, message: Json<Data>) -> Result<JsonVal
author: user.id, author: user.id,
content: Content::Text(message.content.clone()), content: Content::Text(message.content.clone()),
attachment,
nonce: Some(message.nonce.clone()), nonce: Some(message.nonce.clone()),
edited: None, edited: None,
embeds: None embeds: None,
attachments: if attachments.len() > 0 { Some(attachments) } else { None },
mentions: if mentions.len() > 0 {
Some(mentions.into_iter().collect::<Vec<String>>())
} else {
None
},
replies: if replies.len() > 0 {
Some(replies.into_iter().collect::<Vec<String>>())
} else {
None
},
}; };
msg.clone().publish(&target).await?; msg.clone().publish(&target, perm.get_embed_links()).await?;
Ok(json!(msg)) Ok(json!(msg))
} }
use rocket::Route; use rocket::Route;
mod delete_channel; mod channel_ack;
mod edit_channel; mod channel_delete;
mod fetch_channel; mod channel_edit;
mod channel_fetch;
mod members_fetch;
mod group_add_member; mod group_add_member;
mod group_create; mod group_create;
mod group_remove_member; mod group_remove_member;
mod join_call; mod invite_create;
mod voice_join;
mod message_delete; mod message_delete;
mod message_edit; mod message_edit;
mod message_fetch; mod message_fetch;
mod message_query; mod message_query;
mod message_search;
mod message_query_stale; mod message_query_stale;
mod message_send; mod message_send;
mod permissions_set;
mod permissions_set_default;
pub fn routes() -> Vec<Route> { pub fn routes() -> Vec<Route> {
routes![ routes![
fetch_channel::req, channel_ack::req,
delete_channel::req, channel_fetch::req,
edit_channel::req, members_fetch::req,
channel_delete::req,
channel_edit::req,
invite_create::req,
message_send::req, message_send::req,
message_query::req, message_query::req,
message_search::req,
message_query_stale::req, message_query_stale::req,
message_fetch::req, message_fetch::req,
message_edit::req, message_edit::req,
...@@ -28,6 +38,8 @@ pub fn routes() -> Vec<Route> { ...@@ -28,6 +38,8 @@ pub fn routes() -> Vec<Route> {
group_create::req, group_create::req,
group_add_member::req, group_add_member::req,
group_remove_member::req, group_remove_member::req,
join_call::req, voice_join::req,
permissions_set::req,
permissions_set_default::req,
] ]
} }
use mongodb::bson::doc;
use rocket_contrib::json::Json;
use serde::{Serialize, Deserialize};
use validator::Contains;
use crate::database::*;
use crate::database::permissions::channel::ChannelPermission;
use crate::notifications::events::ClientboundNotification;
use crate::util::result::{Error, Result};
#[derive(Serialize, Deserialize)]
pub struct Data {
permissions: u32
}
#[put("/<target>/permissions/<role>", data = "<data>", rank = 2)]
pub async fn req(user: User, target: Ref, role: String, data: Json<Data>) -> Result<()> {
let target = target.fetch_channel().await?;
match target {
Channel::TextChannel { id, server, mut role_permissions, .. }
| Channel::VoiceChannel { id, server, mut role_permissions, .. } => {
let target = Ref::from_unchecked(server).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.has_element(&role) {
return Err(Error::NotFound);
}
let permissions: u32 = ChannelPermission::View as u32 | data.permissions;
get_collection("channels")
.update_one(
doc! { "_id": &id },
doc! {
"$set": {
"role_permissions.".to_owned() + &role: permissions as i32
}
},
None
)
.await
.map_err(|_| Error::DatabaseError {
operation: "update_one",
with: "channel"
})?;
role_permissions.insert(role, permissions as i32);
ClientboundNotification::ChannelUpdate {
id: id.clone(),
data: json!({
"role_permissions": role_permissions
}),
clear: None
}
.publish(id);
Ok(())
}
_ => Err(Error::InvalidOperation)
}
}
use mongodb::bson::doc;
use rocket_contrib::json::Json;
use serde::{Serialize, Deserialize};
use crate::database::*;
use crate::database::permissions::channel::{ ChannelPermission, DEFAULT_PERMISSION_DM };
use crate::notifications::events::ClientboundNotification;
use crate::util::result::{Error, Result};
#[derive(Serialize, Deserialize)]
pub struct Data {
permissions: u32
}
#[put("/<target>/permissions/default", data = "<data>", rank = 1)]
pub async fn req(user: User, target: Ref, data: Json<Data>) -> Result<()> {
let target = target.fetch_channel().await?;
match target {
Channel::Group { id, owner, .. } => {
if user.id == owner {
let permissions: u32 = ChannelPermission::View as u32 | (data.permissions & *DEFAULT_PERMISSION_DM);
get_collection("channels")
.update_one(
doc! { "_id": &id },
doc! {
"$set": {
"permissions": permissions as i32
}
},
None
)
.await
.map_err(|_| Error::DatabaseError {
operation: "update_one",
with: "channel"
})?;
ClientboundNotification::ChannelUpdate {
id: id.clone(),
data: json!({
"permissions": permissions as i32
}),
clear: None
}
.publish(id);
Ok(())
} else {
Err(Error::MissingPermission)
}
}
Channel::TextChannel { id, server, .. }
| Channel::VoiceChannel { id, server, .. } => {
let target = Ref::from_unchecked(server).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 permissions: u32 = ChannelPermission::View as u32 | data.permissions;
get_collection("channels")
.update_one(
doc! { "_id": &id },
doc! {
"$set": {
"default_permissions": permissions as i32
}
},
None
)
.await
.map_err(|_| Error::DatabaseError {
operation: "update_one",
with: "channel"
})?;
ClientboundNotification::ChannelUpdate {
id: id.clone(),
data: json!({
"default_permissions": permissions as i32
}),
clear: None
}
.publish(id);
Ok(())
}
_ => Err(Error::InvalidOperation)
}
}
use crate::database::*; use crate::database::*;
use crate::util::result::{Error, Result}; use crate::util::result::{Error, Result};
use crate::util::variables::{USE_VOSO, VOSO_URL, VOSO_MANAGE_TOKEN}; use crate::util::variables::{USE_VOSO, VOSO_MANAGE_TOKEN, VOSO_URL};
use serde::{Serialize, Deserialize};
use rocket_contrib::json::JsonValue; use rocket_contrib::json::JsonValue;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
struct CreateUserResponse { struct CreateUserResponse {
token: String token: String,
} }
#[post("/<target>/join_call")] #[post("/<target>/join_call")]
pub async fn req(user: User, target: Ref) -> Result<JsonValue> { pub async fn req(user: User, target: Ref) -> Result<JsonValue> {
if !*USE_VOSO { if !*USE_VOSO {
return Err(Error::VosoUnavailable) return Err(Error::VosoUnavailable);
} }
let target = target.fetch_channel().await?; let target = target.fetch_channel().await?;
match target {
Channel::SavedMessages { .. } | Channel::TextChannel { .. } => {
return Err(Error::CannotJoinCall)
}
_ => {}
}
let perm = permissions::PermissionCalculator::new(&user) let perm = permissions::PermissionCalculator::new(&user)
.with_channel(&target) .with_channel(&target)
.for_channel() .for_channel()
.await?; .await?;
if !perm.get_voice_call() { if !perm.get_voice_call() {
return Err(Error::MissingPermission) return Err(Error::MissingPermission);
} }
// To join a call: // To join a call:
...@@ -32,39 +39,51 @@ pub async fn req(user: User, target: Ref) -> Result<JsonValue> { ...@@ -32,39 +39,51 @@ pub async fn req(user: User, target: Ref) -> Result<JsonValue> {
let client = reqwest::Client::new(); let client = reqwest::Client::new();
let result = client let result = client
.get(&format!("{}/room/{}", *VOSO_URL, target.id())) .get(&format!("{}/room/{}", *VOSO_URL, target.id()))
.header(reqwest::header::AUTHORIZATION, VOSO_MANAGE_TOKEN.to_string()) .header(
reqwest::header::AUTHORIZATION,
VOSO_MANAGE_TOKEN.to_string(),
)
.send() .send()
.await; .await;
match result { match result {
Err(_) => return Err(Error::VosoUnavailable), Err(_) => return Err(Error::VosoUnavailable),
Ok(result) => { Ok(result) => match result.status() {
match result.status() { reqwest::StatusCode::OK => (),
reqwest::StatusCode::OK => (), reqwest::StatusCode::NOT_FOUND => {
reqwest::StatusCode::NOT_FOUND => { if let Err(_) = client
if let Err(_) = client .post(&format!("{}/room/{}", *VOSO_URL, target.id()))
.post(&format!("{}/room/{}", *VOSO_URL, target.id())) .header(
.header(reqwest::header::AUTHORIZATION, VOSO_MANAGE_TOKEN.to_string()) reqwest::header::AUTHORIZATION,
.send() VOSO_MANAGE_TOKEN.to_string(),
.await { )
return Err(Error::VosoUnavailable) .send()
} .await
}, {
_ => return Err(Error::VosoUnavailable) return Err(Error::VosoUnavailable);
}
} }
} _ => return Err(Error::VosoUnavailable),
},
} }
// Then create a user for the room. // Then create a user for the room.
if let Ok(response) = client if let Ok(response) = client
.post(&format!("{}/room/{}/user/{}", *VOSO_URL, target.id(), user.id)) .post(&format!(
.header(reqwest::header::AUTHORIZATION, VOSO_MANAGE_TOKEN.to_string()) "{}/room/{}/user/{}",
*VOSO_URL,
target.id(),
user.id
))
.header(
reqwest::header::AUTHORIZATION,
VOSO_MANAGE_TOKEN.to_string(),
)
.send() .send()
.await { .await
let res: CreateUserResponse = response.json() {
.await let res: CreateUserResponse = response.json().await.map_err(|_| Error::InvalidOperation)?;
.map_err(|_| Error::InvalidOperation)?;
Ok(json!(res)) Ok(json!(res))
} else { } else {
Err(Error::VosoUnavailable) Err(Error::VosoUnavailable)
......
use crate::database::*;
use crate::util::result::{Error, Result};
#[delete("/<target>")]
pub async fn req(user: User, target: Ref) -> Result<()> {
let target = target.fetch_invite().await?;
if target.creator() == &user.id {
target.delete().await
} else {
match &target {
Invite::Server { server, .. } => {
let server = Ref::from_unchecked(server.clone()).fetch_server().await?;
let perm = permissions::PermissionCalculator::new(&user)
.with_server(&server)
.for_server()
.await?;
if !perm.get_manage_server() {
return Err(Error::MissingPermission);
}
target.delete().await
}
_ => unreachable!(),
}
}
}
use crate::database::*;
use crate::util::result::Result;
use rocket_contrib::json::JsonValue;
use serde::Serialize;
#[derive(Serialize, Debug, Clone)]
#[serde(tag = "type")]
pub enum InviteResponse {
Server {
server_id: String,
server_name: String,
#[serde(skip_serializing_if = "Option::is_none")]
server_icon: Option<File>,
#[serde(skip_serializing_if = "Option::is_none")]
server_banner: Option<File>,
channel_id: String,
channel_name: String,
#[serde(skip_serializing_if = "Option::is_none")]
channel_description: Option<String>,
user_name: String,
#[serde(skip_serializing_if = "Option::is_none")]
user_avatar: Option<File>,
member_count: i64,
},
}
#[get("/<target>")]
pub async fn req(target: Ref) -> Result<JsonValue> {
let target = target.fetch_invite().await?;
match target {
Invite::Server {
channel, creator, ..
} => {
let channel = Ref::from_unchecked(channel).fetch_channel().await?;
let creator = Ref::from_unchecked(creator).fetch_user().await?;
match channel {
Channel::TextChannel { id, server, name, description, .. }
| Channel::VoiceChannel { id, server, name, description, .. } => {
let server = Ref::from_unchecked(server).fetch_server().await?;
Ok(json!(InviteResponse::Server {
member_count: Server::get_member_count(&server.id).await?,
server_id: server.id,
server_name: server.name,
server_icon: server.icon,
server_banner: server.banner,
channel_id: id,
channel_name: name,
channel_description: description,
user_name: creator.username,
user_avatar: creator.avatar
}))
}
_ => unreachable!()
}
}
_ => unreachable!(),
}
}