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 1336 additions and 3 deletions
use crate::util::result::{Error, Result};
use crate::{database::*, notifications::events::ClientboundNotification};
use mongodb::bson::doc;
#[delete("/<target>/recipients/<member>")]
pub async fn req(user: User, target: Ref, member: Ref) -> Result<()> {
if &user.id == &member.id {
Err(Error::CannotRemoveYourself)?
}
let channel = target.fetch_channel().await?;
if let Channel::Group {
id,
owner,
recipients,
..
} = &channel
{
if &user.id != owner {
// figure out if we want to use perm system here
Err(Error::MissingPermission)?
}
if recipients.iter().find(|x| *x == &member.id).is_none() {
Err(Error::NotInGroup)?
}
get_collection("channels")
.update_one(
doc! {
"_id": &id
},
doc! {
"$pull": {
"recipients": &member.id
}
},
None,
)
.await
.map_err(|_| Error::DatabaseError {
operation: "update_one",
with: "channel",
})?;
ClientboundNotification::ChannelGroupLeave {
id: id.clone(),
user: member.id.clone(),
}
.publish(id.clone());
Content::SystemMessage(SystemMessage::UserRemove {
id: member.id,
by: user.id,
})
.send_as_system(&channel)
.await
.ok();
Ok(())
} else {
Err(Error::InvalidOperation)
}
}
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)
}
}
use crate::database::*;
use crate::util::result::{Error, Result};
use mongodb::bson::doc;
#[delete("/<target>/messages/<msg>")]
pub async fn req(user: User, target: Ref, msg: Ref) -> Result<()> {
let channel = target.fetch_channel().await?;
channel.has_messaging()?;
let perm = permissions::PermissionCalculator::new(&user)
.with_channel(&channel)
.for_channel()
.await?;
if !perm.get_view() {
Err(Error::MissingPermission)?
}
let message = msg.fetch_message(&channel).await?;
if message.author != user.id && !perm.get_manage_messages() {
match channel {
Channel::SavedMessages { .. } => unreachable!(),
_ => Err(Error::CannotEditMessage)?,
}
}
message.delete().await
}
use crate::database::*;
use crate::util::result::{Error, Result};
use chrono::Utc;
use mongodb::bson::{doc, Bson, DateTime, 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 = 2000))]
content: String,
}
#[patch("/<target>/messages/<msg>", data = "<edit>")]
pub async fn req(user: User, target: Ref, msg: Ref, edit: Json<Data>) -> Result<()> {
edit.validate()
.map_err(|error| Error::FailedValidation { error })?;
let channel = target.fetch_channel().await?;
channel.has_messaging()?;
let perm = permissions::PermissionCalculator::new(&user)
.with_channel(&channel)
.for_channel()
.await?;
if !perm.get_view() {
Err(Error::MissingPermission)?
}
let mut message = msg.fetch_message(&channel).await?;
if message.author != user.id {
Err(Error::CannotEditMessage)?
}
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")
.update_one(
doc! {
"_id": &message.id
},
doc! {
"$set": set
},
None,
)
.await
.map_err(|_| Error::DatabaseError {
operation: "update_one",
with: "message",
})?;
message.publish_update(update).await
}
use crate::database::*;
use crate::util::result::{Error, Result};
use rocket_contrib::json::JsonValue;
#[get("/<target>/messages/<msg>")]
pub async fn req(user: User, target: Ref, msg: Ref) -> Result<JsonValue> {
let channel = target.fetch_channel().await?;
channel.has_messaging()?;
let perm = permissions::PermissionCalculator::new(&user)
.with_channel(&channel)
.for_channel()
.await?;
if !perm.get_view() {
Err(Error::MissingPermission)?
}
let message = msg.fetch_message(&channel).await?;
Ok(json!(message))
}
use std::collections::HashSet;
use crate::database::*;
use crate::util::result::{Error, Result};
use futures::{StreamExt, try_join};
use mongodb::{
bson::{doc, from_document},
options::FindOptions,
};
use rocket::request::Form;
use rocket_contrib::json::JsonValue;
use serde::{Deserialize, Serialize};
use validator::Validate;
#[derive(Serialize, Deserialize, FromFormValue)]
pub enum Sort {
Latest,
Oldest,
}
#[derive(Validate, Serialize, Deserialize, FromForm)]
pub struct Options {
#[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>,
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..>")]
pub async fn req(user: User, target: Ref, options: Form<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 collection = get_collection("messages");
let limit = options.limit.unwrap_or(50);
let channel = target.id();
if let Some(nearby) = &options.nearby {
let mut cursors = try_join!(
collection.find(
doc! {
"channel": channel,
"_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(),
)
)
.map_err(|_| Error::DatabaseError {
operation: "find",
with: "messages",
})?;
while let Some(result) = cursors.0.next().await {
if let Ok(doc) = result {
messages.push(
from_document::<Message>(doc).map_err(|_| Error::DatabaseError {
operation: "from_document",
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",
})?,
);
}
}
}
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 crate::database::*;
use crate::util::result::{Error, Result};
use futures::StreamExt;
use mongodb::bson::{doc, from_document};
use rocket_contrib::json::{Json, JsonValue};
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
pub struct Options {
ids: Vec<String>,
}
#[post("/<target>/messages/stale", data = "<data>")]
pub async fn req(user: User, target: Ref, data: Json<Options>) -> Result<JsonValue> {
if data.ids.len() > 150 {
return Err(Error::TooManyIds);
}
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 cursor = get_collection("messages")
.find(
doc! {
"_id": {
"$in": &data.ids
},
"channel": target.id()
},
None,
)
.await
.map_err(|_| Error::DatabaseError {
operation: "find",
with: "messages",
})?;
let mut updated = vec![];
let mut found_ids = vec![];
while let Some(result) = cursor.next().await {
if let Ok(doc) = result {
let msg = from_document::<Message>(doc).map_err(|_| Error::DatabaseError {
operation: "from_document",
with: "message",
})?;
found_ids.push(msg.id.clone());
if msg.edited.is_some() {
updated.push(msg);
}
}
}
let mut deleted = vec![];
for id in &data.ids {
if found_ids.iter().find(|x| *x == id).is_none() {
deleted.push(id);
}
}
Ok(json!({
"updated": updated,
"deleted": deleted
}))
}
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::util::result::{Error, Result};
use mongodb::{bson::doc, options::FindOneOptions};
use regex::Regex;
use rocket_contrib::json::{Json, JsonValue};
use serde::{Deserialize, Serialize};
use ulid::Ulid;
use validator::Validate;
#[derive(Serialize, Deserialize)]
pub struct Reply {
id: String,
mention: bool
}
#[derive(Validate, Serialize, Deserialize)]
pub struct Data {
#[validate(length(min = 0, max = 2000))]
content: String,
// Maximum length of 36 allows both ULIDs and UUIDs.
#[validate(length(min = 1, max = 36))]
nonce: String,
#[validate(length(min = 1, max = 128))]
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>")]
pub async fn req(user: User, target: Ref, message: Json<Data>) -> Result<JsonValue> {
let message = message.into_inner();
message
.validate()
.map_err(|error| Error::FailedValidation { error })?;
if message.content.len() == 0
&& (message.attachments.is_none() || message.attachments.as_ref().unwrap().len() == 0)
{
return Err(Error::EmptyMessage);
}
let target = target.fetch_channel().await?;
target.has_messaging()?;
let perm = permissions::PermissionCalculator::new(&user)
.with_channel(&target)
.for_channel()
.await?;
if !perm.get_send_message() {
return Err(Error::MissingPermission)
}
if get_collection("messages")
.find_one(
doc! {
"nonce": &message.nonce
},
FindOneOptions::builder()
.projection(doc! { "_id": 1 })
.build(),
)
.await
.map_err(|_| Error::DatabaseError {
operation: "find_one",
with: "message",
})?
.is_some()
{
Err(Error::DuplicateNonce)?
}
let id = Ulid::new().to_string();
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
.push(File::find_and_use(attachment_id, "attachments", "message", &id).await?);
}
}
let msg = Message {
id,
channel: target.id().to_string(),
author: user.id,
content: Content::Text(message.content.clone()),
nonce: Some(message.nonce.clone()),
edited: 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, perm.get_embed_links()).await?;
Ok(json!(msg))
}
use rocket::Route;
mod channel_ack;
mod channel_delete;
mod channel_edit;
mod channel_fetch;
mod members_fetch;
mod group_add_member;
mod group_create;
mod group_remove_member;
mod invite_create;
mod voice_join;
mod message_delete;
mod message_edit;
mod message_fetch;
mod message_query;
mod message_search;
mod message_query_stale;
mod message_send;
mod permissions_set;
mod permissions_set_default;
pub fn routes() -> Vec<Route> {
routes![
channel_ack::req,
channel_fetch::req,
members_fetch::req,
channel_delete::req,
channel_edit::req,
invite_create::req,
message_send::req,
message_query::req,
message_search::req,
message_query_stale::req,
message_fetch::req,
message_edit::req,
message_delete::req,
group_create::req,
group_add_member::req,
group_remove_member::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::util::result::{Error, Result};
use crate::util::variables::{USE_VOSO, VOSO_MANAGE_TOKEN, VOSO_URL};
use rocket_contrib::json::JsonValue;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
struct CreateUserResponse {
token: String,
}
#[post("/<target>/join_call")]
pub async fn req(user: User, target: Ref) -> Result<JsonValue> {
if !*USE_VOSO {
return Err(Error::VosoUnavailable);
}
let target = target.fetch_channel().await?;
match target {
Channel::SavedMessages { .. } | Channel::TextChannel { .. } => {
return Err(Error::CannotJoinCall)
}
_ => {}
}
let perm = permissions::PermissionCalculator::new(&user)
.with_channel(&target)
.for_channel()
.await?;
if !perm.get_voice_call() {
return Err(Error::MissingPermission);
}
// To join a call:
// - Check if the room exists.
// - If not, create it.
let client = reqwest::Client::new();
let result = client
.get(&format!("{}/room/{}", *VOSO_URL, target.id()))
.header(
reqwest::header::AUTHORIZATION,
VOSO_MANAGE_TOKEN.to_string(),
)
.send()
.await;
match result {
Err(_) => return Err(Error::VosoUnavailable),
Ok(result) => match result.status() {
reqwest::StatusCode::OK => (),
reqwest::StatusCode::NOT_FOUND => {
if let Err(_) = client
.post(&format!("{}/room/{}", *VOSO_URL, target.id()))
.header(
reqwest::header::AUTHORIZATION,
VOSO_MANAGE_TOKEN.to_string(),
)
.send()
.await
{
return Err(Error::VosoUnavailable);
}
}
_ => return Err(Error::VosoUnavailable),
},
}
// Then create a user for the room.
if let Ok(response) = client
.post(&format!(
"{}/room/{}/user/{}",
*VOSO_URL,
target.id(),
user.id
))
.header(
reqwest::header::AUTHORIZATION,
VOSO_MANAGE_TOKEN.to_string(),
)
.send()
.await
{
let res: CreateUserResponse = response.json().await.map_err(|_| Error::InvalidOperation)?;
Ok(json!(res))
} else {
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!(),
}
}
use crate::database::*;
use crate::util::result::Result;
use rocket_contrib::json::JsonValue;
#[post("/<target>")]
pub async fn req(user: User, target: Ref) -> Result<JsonValue> {
let target = target.fetch_invite().await?;
match target {
Invite::Server { channel, .. } => {
let channel = Ref::from_unchecked(channel).fetch_channel().await?;
let server = match &channel {
Channel::TextChannel { server, .. }
| Channel::VoiceChannel { server, .. } => {
Ref::from_unchecked(server.clone()).fetch_server().await?
}
_ => unreachable!()
};
server.join_member(&user.id).await?;
Ok(json!({
"type": "Server",
"channel": channel,
"server": server
}))
}
_ => unreachable!(),
}
}
use rocket::Route;
mod invite_delete;
mod invite_fetch;
mod invite_join;
pub fn routes() -> Vec<Route> {
routes![invite_fetch::req, invite_join::req, invite_delete::req]
}
pub use rocket::http::Status;
pub use rocket::response::Redirect;
use rocket::Rocket;
mod account;
mod channels;
mod invites;
mod onboard;
mod push;
mod root;
mod servers;
mod sync;
mod users;
pub fn mount(rocket: Rocket) -> Rocket {
rocket
.mount("/api/account", routes![ account::root, account::create ])
rocket
.mount("/", routes![root::root])
.mount("/onboard", onboard::routes())
.mount("/users", users::routes())
.mount("/channels", channels::routes())
.mount("/servers", servers::routes())
.mount("/invites", invites::routes())
.mount("/push", push::routes())
.mount("/sync", sync::routes())
}
use crate::database::*;
use crate::util::result::{Error, Result};
use mongodb::bson::doc;
use rauth::auth::Session;
use regex::Regex;
use rocket_contrib::json::Json;
use serde::{Deserialize, Serialize};
use validator::Validate;
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: String,
}
#[post("/complete", data = "<data>")]
pub async fn req(session: Session, user: Option<User>, data: Json<Data>) -> Result<()> {
if user.is_some() {
Err(Error::AlreadyOnboarded)?
}
data.validate()
.map_err(|error| Error::FailedValidation { error })?;
if User::is_username_taken(&data.username).await? {
return Err(Error::UsernameTaken);
}
get_collection("users")
.insert_one(
doc! {
"_id": session.user_id,
"username": &data.username
},
None,
)
.await
.map_err(|_| Error::DatabaseError {
operation: "insert_one",
with: "user",
})?;
Ok(())
}