Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fae0198343 | ||
|
|
10f116b4a3 | ||
|
|
6716a2d32b | ||
|
|
6cc92b877e | ||
|
|
cc0307f702 | ||
|
|
274d6f2b5a | ||
|
|
0f100213ba | ||
|
|
7293abfc53 | ||
|
|
cf7bb832da | ||
|
|
efc3794f3d | ||
|
|
a319e72655 | ||
|
|
2173b1e9f8 | ||
|
|
c086fe7ac4 | ||
|
|
fa960ebc94 | ||
|
|
b47067b311 | ||
|
|
2e996a487b | ||
|
|
92bface6ae | ||
|
|
8cfa5d7091 | ||
|
|
59b18fd376 | ||
|
|
f135a57a9b | ||
|
|
5da26cb833 | ||
|
|
c8981ac695 | ||
|
|
33b0658680 | ||
|
|
81002c75d2 | ||
|
|
f163cb65de | ||
|
|
f7bcd3ad93 |
No files matched your search
Vendored
+5
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"rust-analyzer.diagnostics.disabled": [
|
||||||
|
"unresolved-macro-call"
|
||||||
|
]
|
||||||
|
}
|
||||||
Generated
+11
-1
@@ -1389,6 +1389,15 @@ version = "0.5.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3"
|
checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "linkify"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1986921c3c13e81df623c66a298d4b130c061bcb98a01f5b2d3ac402b1649a7f"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lock_api"
|
name = "lock_api"
|
||||||
version = "0.3.4"
|
version = "0.3.4"
|
||||||
@@ -2475,7 +2484,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "revolt"
|
name = "revolt"
|
||||||
version = "0.4.0-alpha.6"
|
version = "0.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-std",
|
"async-std",
|
||||||
"async-tungstenite",
|
"async-tungstenite",
|
||||||
@@ -2490,6 +2499,7 @@ dependencies = [
|
|||||||
"impl_ops",
|
"impl_ops",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"lettre",
|
"lettre",
|
||||||
|
"linkify",
|
||||||
"log",
|
"log",
|
||||||
"many-to-many",
|
"many-to-many",
|
||||||
"md5",
|
"md5",
|
||||||
|
|||||||
+5
-1
@@ -1,6 +1,9 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "revolt"
|
name = "revolt"
|
||||||
version = "0.4.0-alpha.6"
|
# To help optimise CI and Docker builds.
|
||||||
|
# Version here is left as 0.0.0, please
|
||||||
|
# adjust and run ./set_version.sh instead.
|
||||||
|
version = "0.0.0"
|
||||||
authors = ["Paul Makles <paulmakles@gmail.com>"]
|
authors = ["Paul Makles <paulmakles@gmail.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
@@ -14,6 +17,7 @@ ulid = "0.4.1"
|
|||||||
rand = "0.7.3"
|
rand = "0.7.3"
|
||||||
time = "0.2.16"
|
time = "0.2.16"
|
||||||
base64 = "0.13.0"
|
base64 = "0.13.0"
|
||||||
|
linkify = "0.6.0"
|
||||||
dotenv = "0.15.0"
|
dotenv = "0.15.0"
|
||||||
futures = "0.3.8"
|
futures = "0.3.8"
|
||||||
chrono = "0.4.15"
|
chrono = "0.4.15"
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ WORKDIR /home/rust/src
|
|||||||
RUN USER=root cargo new --bin revolt
|
RUN USER=root cargo new --bin revolt
|
||||||
WORKDIR /home/rust/src/revolt
|
WORKDIR /home/rust/src/revolt
|
||||||
COPY Cargo.toml Cargo.lock ./
|
COPY Cargo.toml Cargo.lock ./
|
||||||
|
COPY src/bin/dummy.rs ./src/bin/dummy.rs
|
||||||
|
RUN cargo build --release --bin dummy
|
||||||
|
|
||||||
COPY assets/templates ./assets/templates
|
COPY assets/templates ./assets/templates
|
||||||
COPY src ./src
|
COPY src ./src
|
||||||
RUN cargo build --release
|
RUN cargo build --release
|
||||||
|
|||||||
Executable
+5
@@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
source set_version.sh
|
||||||
|
|
||||||
|
docker build -t revoltchat/server:${version} . &&
|
||||||
|
docker push revoltchat/server:${version}
|
||||||
Executable
+3
@@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
export version=0.4.1-alpha.11
|
||||||
|
echo "pub const VERSION: &str = \"${version}\";" > src/version.rs
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
fn main() {}
|
||||||
@@ -1,9 +1,14 @@
|
|||||||
|
use mongodb::bson::{doc, from_document};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::database::*;
|
||||||
|
use crate::util::result::{Error, Result};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
#[serde(tag = "type")]
|
#[serde(tag = "type")]
|
||||||
enum Metadata {
|
enum Metadata {
|
||||||
File,
|
File,
|
||||||
|
Text,
|
||||||
Image { width: isize, height: isize },
|
Image { width: isize, height: isize },
|
||||||
Video { width: isize, height: isize },
|
Video { width: isize, height: isize },
|
||||||
Audio,
|
Audio,
|
||||||
@@ -13,9 +18,98 @@ enum Metadata {
|
|||||||
pub struct File {
|
pub struct File {
|
||||||
#[serde(rename = "_id")]
|
#[serde(rename = "_id")]
|
||||||
pub id: String,
|
pub id: String,
|
||||||
|
tag: String,
|
||||||
filename: String,
|
filename: String,
|
||||||
metadata: Metadata,
|
metadata: Metadata,
|
||||||
content_type: String,
|
content_type: String,
|
||||||
|
size: isize,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
deleted: Option<bool>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
message_id: Option<String>,
|
message_id: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
user_id: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
server_id: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
object_id: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl File {
|
||||||
|
pub async fn find_and_use(
|
||||||
|
attachment_id: &str,
|
||||||
|
tag: &str,
|
||||||
|
parent_type: &str,
|
||||||
|
parent_id: &str,
|
||||||
|
) -> Result<File> {
|
||||||
|
let attachments = get_collection("attachments");
|
||||||
|
let key = format!("{}_id", parent_type);
|
||||||
|
if let Some(doc) = attachments
|
||||||
|
.find_one(
|
||||||
|
doc! {
|
||||||
|
"_id": attachment_id,
|
||||||
|
"tag": &tag,
|
||||||
|
key.clone(): {
|
||||||
|
"$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",
|
||||||
|
})?;
|
||||||
|
|
||||||
|
attachments
|
||||||
|
.update_one(
|
||||||
|
doc! {
|
||||||
|
"_id": &attachment.id
|
||||||
|
},
|
||||||
|
doc! {
|
||||||
|
"$set": {
|
||||||
|
key: &parent_id
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|_| Error::DatabaseError {
|
||||||
|
operation: "update_one",
|
||||||
|
with: "attachment",
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(attachment)
|
||||||
|
} else {
|
||||||
|
Err(Error::UnknownAttachment)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn delete(&self) -> Result<()> {
|
||||||
|
get_collection("attachments")
|
||||||
|
.update_one(
|
||||||
|
doc! {
|
||||||
|
"_id": &self.id
|
||||||
|
},
|
||||||
|
doc! {
|
||||||
|
"$set": {
|
||||||
|
"deleted": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map(|_| ())
|
||||||
|
.map_err(|_| Error::DatabaseError {
|
||||||
|
operation: "update_one",
|
||||||
|
with: "attachment",
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,11 @@
|
|||||||
use crate::database::*;
|
use crate::database::*;
|
||||||
use crate::notifications::events::ClientboundNotification;
|
use crate::notifications::events::ClientboundNotification;
|
||||||
use crate::util::result::{Error, Result};
|
use crate::util::result::{Error, Result};
|
||||||
use mongodb::bson::{doc, from_document, to_document};
|
use futures::StreamExt;
|
||||||
|
use mongodb::{
|
||||||
|
bson::{doc, from_document, to_document, Document},
|
||||||
|
options::FindOptions,
|
||||||
|
};
|
||||||
use rocket_contrib::json::JsonValue;
|
use rocket_contrib::json::JsonValue;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
@@ -24,6 +28,7 @@ pub enum Channel {
|
|||||||
DirectMessage {
|
DirectMessage {
|
||||||
#[serde(rename = "_id")]
|
#[serde(rename = "_id")]
|
||||||
id: String,
|
id: String,
|
||||||
|
|
||||||
active: bool,
|
active: bool,
|
||||||
recipients: Vec<String>,
|
recipients: Vec<String>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
@@ -34,10 +39,14 @@ pub enum Channel {
|
|||||||
id: String,
|
id: String,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
nonce: Option<String>,
|
nonce: Option<String>,
|
||||||
|
|
||||||
name: String,
|
name: String,
|
||||||
owner: String,
|
owner: String,
|
||||||
description: String,
|
description: String,
|
||||||
recipients: Vec<String>,
|
recipients: Vec<String>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
icon: Option<File>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
last_message: Option<LastMessage>,
|
last_message: Option<LastMessage>,
|
||||||
},
|
},
|
||||||
@@ -84,10 +93,7 @@ impl Channel {
|
|||||||
})?;
|
})?;
|
||||||
|
|
||||||
let channel_id = self.id().to_string();
|
let channel_id = self.id().to_string();
|
||||||
ClientboundNotification::ChannelCreate(self)
|
ClientboundNotification::ChannelCreate(self).publish(channel_id);
|
||||||
.publish(channel_id)
|
|
||||||
.await
|
|
||||||
.ok();
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -97,17 +103,65 @@ impl Channel {
|
|||||||
ClientboundNotification::ChannelUpdate {
|
ClientboundNotification::ChannelUpdate {
|
||||||
id: id.clone(),
|
id: id.clone(),
|
||||||
data,
|
data,
|
||||||
|
clear: None,
|
||||||
}
|
}
|
||||||
.publish(id)
|
.publish(id);
|
||||||
.await
|
|
||||||
.ok();
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn delete(&self) -> Result<()> {
|
pub async fn delete(&self) -> Result<()> {
|
||||||
let id = self.id();
|
let id = self.id();
|
||||||
get_collection("messages")
|
let messages = get_collection("messages");
|
||||||
|
|
||||||
|
// Check if there are any attachments we need to delete.
|
||||||
|
let message_ids = messages
|
||||||
|
.find(
|
||||||
|
doc! {
|
||||||
|
"channel": id,
|
||||||
|
"attachment": {
|
||||||
|
"$exists": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
FindOptions::builder().projection(doc! { "_id": 1 }).build(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|_| Error::DatabaseError {
|
||||||
|
operation: "fetch_many",
|
||||||
|
with: "messages",
|
||||||
|
})?
|
||||||
|
.filter_map(async move |s| s.ok())
|
||||||
|
.collect::<Vec<Document>>()
|
||||||
|
.await
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|x| x.get_str("_id").ok().map(|x| x.to_string()))
|
||||||
|
.collect::<Vec<String>>();
|
||||||
|
|
||||||
|
// If we found any, mark them as deleted.
|
||||||
|
if message_ids.len() > 0 {
|
||||||
|
get_collection("attachments")
|
||||||
|
.update_many(
|
||||||
|
doc! {
|
||||||
|
"message_id": {
|
||||||
|
"$in": message_ids
|
||||||
|
}
|
||||||
|
},
|
||||||
|
doc! {
|
||||||
|
"$set": {
|
||||||
|
"deleted": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|_| Error::DatabaseError {
|
||||||
|
operation: "update_many",
|
||||||
|
with: "attachments",
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// And then delete said messages.
|
||||||
|
messages
|
||||||
.delete_many(
|
.delete_many(
|
||||||
doc! {
|
doc! {
|
||||||
"channel": id
|
"channel": id
|
||||||
@@ -133,10 +187,13 @@ impl Channel {
|
|||||||
with: "channel",
|
with: "channel",
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
ClientboundNotification::ChannelDelete { id: id.to_string() }
|
ClientboundNotification::ChannelDelete { id: id.to_string() }.publish(id.to_string());
|
||||||
.publish(id.to_string())
|
|
||||||
.await
|
if let Channel::Group { icon, .. } = self {
|
||||||
.ok();
|
if let Some(attachment) = icon {
|
||||||
|
attachment.delete().await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,144 @@
|
|||||||
|
use crate::util::{
|
||||||
|
result::{Error, Result},
|
||||||
|
variables::JANUARY_URL,
|
||||||
|
};
|
||||||
|
use linkify::{LinkFinder, LinkKind};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
pub enum ImageSize {
|
||||||
|
Large,
|
||||||
|
Preview,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
pub struct Image {
|
||||||
|
pub url: String,
|
||||||
|
pub width: isize,
|
||||||
|
pub height: isize,
|
||||||
|
pub size: ImageSize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
pub struct Video {
|
||||||
|
pub url: String,
|
||||||
|
pub width: isize,
|
||||||
|
pub height: isize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
pub enum TwitchType {
|
||||||
|
Channel,
|
||||||
|
Video,
|
||||||
|
Clip,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
pub enum BandcampType {
|
||||||
|
Album,
|
||||||
|
Track,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
#[serde(tag = "type")]
|
||||||
|
pub enum Special {
|
||||||
|
None,
|
||||||
|
YouTube {
|
||||||
|
id: String,
|
||||||
|
},
|
||||||
|
Twitch {
|
||||||
|
content_type: TwitchType,
|
||||||
|
id: String,
|
||||||
|
},
|
||||||
|
Spotify {
|
||||||
|
content_type: String,
|
||||||
|
id: String,
|
||||||
|
},
|
||||||
|
Soundcloud,
|
||||||
|
Bandcamp {
|
||||||
|
content_type: BandcampType,
|
||||||
|
id: String,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
pub struct Metadata {
|
||||||
|
url: Option<String>,
|
||||||
|
special: Option<Special>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
title: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
description: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
image: Option<Image>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
video: Option<Video>,
|
||||||
|
|
||||||
|
// #[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
// opengraph_type: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
site_name: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
icon_url: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
color: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
#[serde(tag = "type")]
|
||||||
|
pub enum Embed {
|
||||||
|
Website(Metadata),
|
||||||
|
Image(Image),
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Embed {
|
||||||
|
pub async fn generate(content: String) -> Result<Vec<Embed>> {
|
||||||
|
let content = content
|
||||||
|
.split("\n")
|
||||||
|
.map(|v| {
|
||||||
|
// Ignore quoted lines.
|
||||||
|
if let Some(c) = v.chars().next() {
|
||||||
|
if c == '>' {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
v
|
||||||
|
})
|
||||||
|
.collect::<Vec<&str>>()
|
||||||
|
.join("\n");
|
||||||
|
|
||||||
|
// ! FIXME: allow multiple links
|
||||||
|
// ! FIXME: prevent generation if link is surrounded with < >
|
||||||
|
let mut finder = LinkFinder::new();
|
||||||
|
finder.kinds(&[LinkKind::Url]);
|
||||||
|
let links: Vec<_> = finder.links(&content).collect();
|
||||||
|
|
||||||
|
if links.len() == 0 {
|
||||||
|
return Err(Error::LabelMe);
|
||||||
|
}
|
||||||
|
|
||||||
|
let link = &links[0];
|
||||||
|
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
let result = client
|
||||||
|
.get(&format!("{}/embed", *JANUARY_URL))
|
||||||
|
.query(&[("url", link.as_str())])
|
||||||
|
.send()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Err(_) => return Err(Error::LabelMe),
|
||||||
|
Ok(result) => match result.status() {
|
||||||
|
reqwest::StatusCode::OK => {
|
||||||
|
let res: Embed = result.json().await.map_err(|_| Error::InvalidOperation)?;
|
||||||
|
|
||||||
|
Ok(vec![res])
|
||||||
|
}
|
||||||
|
_ => return Err(Error::LabelMe),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::util::variables::VAPID_PRIVATE_KEY;
|
use crate::util::variables::{VAPID_PRIVATE_KEY, USE_JANUARY};
|
||||||
use crate::{
|
use crate::{
|
||||||
database::*,
|
database::*,
|
||||||
notifications::{events::ClientboundNotification, websocket::is_online},
|
notifications::{events::ClientboundNotification, websocket::is_online},
|
||||||
@@ -17,12 +17,6 @@ use web_push::{
|
|||||||
ContentEncoding, SubscriptionInfo, VapidSignatureBuilder, WebPushClient, WebPushMessageBuilder,
|
ContentEncoding, SubscriptionInfo, VapidSignatureBuilder, WebPushClient, WebPushMessageBuilder,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
||||||
#[serde(tag = "type")]
|
|
||||||
pub enum MessageEmbed {
|
|
||||||
Dummy,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
#[serde(tag = "type")]
|
#[serde(tag = "type")]
|
||||||
pub enum SystemMessage {
|
pub enum SystemMessage {
|
||||||
@@ -36,6 +30,10 @@ pub enum SystemMessage {
|
|||||||
UserLeft { id: String },
|
UserLeft { id: String },
|
||||||
#[serde(rename = "channel_renamed")]
|
#[serde(rename = "channel_renamed")]
|
||||||
ChannelRenamed { name: String, by: String },
|
ChannelRenamed { name: String, by: String },
|
||||||
|
#[serde(rename = "channel_description_changed")]
|
||||||
|
ChannelDescriptionChanged { by: String },
|
||||||
|
#[serde(rename = "channel_icon_changed")]
|
||||||
|
ChannelIconChanged { by: String },
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
@@ -60,7 +58,7 @@ pub struct Message {
|
|||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub edited: Option<DateTime>,
|
pub edited: Option<DateTime>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub embeds: Option<MessageEmbed>,
|
pub embeds: Option<Vec<Embed>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Message {
|
impl Message {
|
||||||
@@ -138,11 +136,10 @@ impl Message {
|
|||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.process_embed();
|
||||||
|
|
||||||
let enc = serde_json::to_string(&self).unwrap();
|
let enc = serde_json::to_string(&self).unwrap();
|
||||||
ClientboundNotification::Message(self)
|
ClientboundNotification::Message(self).publish(channel.id().to_string());
|
||||||
.publish(channel.id().to_string())
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Web Push Test Code
|
Web Push Test Code
|
||||||
@@ -162,6 +159,7 @@ impl Message {
|
|||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async_std::task::spawn(async move {
|
||||||
// Fetch their corresponding sessions.
|
// Fetch their corresponding sessions.
|
||||||
if let Ok(mut cursor) = get_collection("accounts")
|
if let Ok(mut cursor) = get_collection("accounts")
|
||||||
.find(
|
.find(
|
||||||
@@ -206,8 +204,10 @@ impl Message {
|
|||||||
|
|
||||||
for subscription in subscriptions {
|
for subscription in subscriptions {
|
||||||
let mut builder = WebPushMessageBuilder::new(&subscription).unwrap();
|
let mut builder = WebPushMessageBuilder::new(&subscription).unwrap();
|
||||||
let sig_builder =
|
let sig_builder = VapidSignatureBuilder::from_pem(
|
||||||
VapidSignatureBuilder::from_pem(std::io::Cursor::new(&key), &subscription)
|
std::io::Cursor::new(&key),
|
||||||
|
&subscription,
|
||||||
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let signature = sig_builder.build().unwrap();
|
let signature = sig_builder.build().unwrap();
|
||||||
builder.set_vapid_signature(signature);
|
builder.set_vapid_signature(signature);
|
||||||
@@ -217,24 +217,85 @@ impl Message {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn publish_update(&self, data: JsonValue) -> Result<()> {
|
pub async fn publish_update(self, data: JsonValue) -> Result<()> {
|
||||||
let channel = self.channel.clone();
|
let channel = self.channel.clone();
|
||||||
ClientboundNotification::MessageUpdate {
|
ClientboundNotification::MessageUpdate {
|
||||||
id: self.id.clone(),
|
id: self.id.clone(),
|
||||||
data,
|
data,
|
||||||
}
|
}
|
||||||
.publish(channel)
|
.publish(channel);
|
||||||
.await
|
self.process_embed();
|
||||||
.ok();
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn process_embed(&self) {
|
||||||
|
if !*USE_JANUARY {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Content::Text(text) = &self.content {
|
||||||
|
let id = self.id.clone();
|
||||||
|
let content = text.clone();
|
||||||
|
let channel = self.channel.clone();
|
||||||
|
async_std::task::spawn(async move {
|
||||||
|
if let Ok(embeds) = Embed::generate(content).await {
|
||||||
|
if let Ok(bson) = to_bson(&embeds) {
|
||||||
|
if let Ok(_) = get_collection("messages")
|
||||||
|
.update_one(
|
||||||
|
doc! {
|
||||||
|
"_id": &id
|
||||||
|
},
|
||||||
|
doc! {
|
||||||
|
"$set": {
|
||||||
|
"embeds": bson
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
ClientboundNotification::MessageUpdate {
|
||||||
|
id,
|
||||||
|
data: json!({ "embeds": embeds }),
|
||||||
|
}
|
||||||
|
.publish(channel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn delete(&self) -> Result<()> {
|
pub async fn delete(&self) -> Result<()> {
|
||||||
|
if let Some(attachment) = &self.attachment {
|
||||||
|
attachment.delete().await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
get_collection("messages")
|
||||||
|
.delete_one(
|
||||||
|
doc! {
|
||||||
|
"_id": &self.id
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|_| Error::DatabaseError {
|
||||||
|
operation: "delete_one",
|
||||||
|
with: "message",
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let channel = self.channel.clone();
|
||||||
|
ClientboundNotification::MessageDelete {
|
||||||
|
id: self.id.clone(),
|
||||||
|
}
|
||||||
|
.publish(channel);
|
||||||
|
|
||||||
if let Some(attachment) = &self.attachment {
|
if let Some(attachment) = &self.attachment {
|
||||||
get_collection("attachments")
|
get_collection("attachments")
|
||||||
.update_one(
|
.update_one(
|
||||||
@@ -255,27 +316,6 @@ impl Message {
|
|||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
|
|
||||||
get_collection("messages")
|
|
||||||
.delete_one(
|
|
||||||
doc! {
|
|
||||||
"_id": &self.id
|
|
||||||
},
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.map_err(|_| Error::DatabaseError {
|
|
||||||
operation: "delete_one",
|
|
||||||
with: "message",
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let channel = self.channel.clone();
|
|
||||||
ClientboundNotification::MessageDelete {
|
|
||||||
id: self.id.clone(),
|
|
||||||
}
|
|
||||||
.publish(channel)
|
|
||||||
.await
|
|
||||||
.ok();
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,13 @@
|
|||||||
mod autumn;
|
mod autumn;
|
||||||
mod channel;
|
mod channel;
|
||||||
mod guild;
|
mod january;
|
||||||
mod message;
|
mod message;
|
||||||
|
mod server;
|
||||||
mod user;
|
mod user;
|
||||||
|
|
||||||
pub use autumn::*;
|
pub use autumn::*;
|
||||||
pub use channel::*;
|
pub use channel::*;
|
||||||
pub use guild::*;
|
pub use january::*;
|
||||||
pub use message::*;
|
pub use message::*;
|
||||||
|
pub use server::*;
|
||||||
pub use user::*;
|
pub use user::*;
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
// use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
/*#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
pub struct MemberCompositeKey {
|
pub struct MemberCompositeKey {
|
||||||
pub guild: String,
|
pub guild: String,
|
||||||
pub user: String,
|
pub user: String,
|
||||||
@@ -27,12 +27,11 @@ pub struct Ban {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
pub struct Guild {
|
pub struct Server {
|
||||||
#[serde(rename = "_id")]
|
#[serde(rename = "_id")]
|
||||||
pub id: String,
|
pub id: String,
|
||||||
// pub nonce: String, used internally
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub name: String,
|
pub nonce: Option<String>,
|
||||||
pub description: String,
|
|
||||||
pub owner: String,
|
pub owner: String,
|
||||||
|
|
||||||
pub channels: Vec<String>,
|
pub channels: Vec<String>,
|
||||||
@@ -40,4 +39,4 @@ pub struct Guild {
|
|||||||
pub bans: Vec<Ban>,
|
pub bans: Vec<Ban>,
|
||||||
|
|
||||||
pub default_permissions: u32,
|
pub default_permissions: u32,
|
||||||
}*/
|
}
|
||||||
@@ -1,12 +1,16 @@
|
|||||||
use mongodb::bson::doc;
|
use futures::StreamExt;
|
||||||
use mongodb::options::{Collation, FindOneOptions};
|
use mongodb::options::{Collation, FindOneOptions};
|
||||||
|
use mongodb::{
|
||||||
|
bson::{doc, from_document},
|
||||||
|
options::FindOptions,
|
||||||
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use validator::Validate;
|
||||||
|
|
||||||
use crate::database::permissions::user::UserPermissions;
|
use crate::database::permissions::user::UserPermissions;
|
||||||
use crate::database::*;
|
use crate::database::*;
|
||||||
use crate::notifications::websocket::is_online;
|
use crate::notifications::websocket::is_online;
|
||||||
use crate::util::result::{Error, Result};
|
use crate::util::result::{Error, Result};
|
||||||
use validator::Validate;
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||||
pub enum RelationshipStatus {
|
pub enum RelationshipStatus {
|
||||||
@@ -38,31 +42,35 @@ pub enum Presence {
|
|||||||
Online,
|
Online,
|
||||||
Idle,
|
Idle,
|
||||||
Busy,
|
Busy,
|
||||||
Invisible
|
Invisible,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Validate, Serialize, Deserialize, Debug)]
|
#[derive(Validate, Serialize, Deserialize, Debug)]
|
||||||
pub struct UserStatus {
|
pub struct UserStatus {
|
||||||
#[validate(length(min = 1, max = 128))]
|
#[validate(length(min = 1, max = 128))]
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
text: Option<String>,
|
pub text: Option<String>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
presence: Option<Presence>
|
pub presence: Option<Presence>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Validate, Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
pub struct UserProfile {
|
pub struct UserProfile {
|
||||||
#[validate(length(min = 1, max = 2000))]
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
content: Option<String>,
|
pub content: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub background: Option<File>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// When changing this struct, update notifications/payload.rs#80
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
pub struct User {
|
pub struct User {
|
||||||
#[serde(rename = "_id")]
|
#[serde(rename = "_id")]
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub username: String,
|
pub username: String,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub avatar: Option<File>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub relations: Option<Vec<Relationship>>,
|
pub relations: Option<Vec<Relationship>>,
|
||||||
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
@@ -82,6 +90,8 @@ pub struct User {
|
|||||||
impl User {
|
impl User {
|
||||||
/// Mutate the user object to include relationship as seen by user.
|
/// Mutate the user object to include relationship as seen by user.
|
||||||
pub fn from(mut self, user: &User) -> User {
|
pub fn from(mut self, user: &User) -> User {
|
||||||
|
self.relationship = Some(RelationshipStatus::None);
|
||||||
|
|
||||||
if self.id == user.id {
|
if self.id == user.id {
|
||||||
self.relationship = Some(RelationshipStatus::User);
|
self.relationship = Some(RelationshipStatus::User);
|
||||||
return self;
|
return self;
|
||||||
@@ -102,12 +112,31 @@ impl User {
|
|||||||
pub fn with(mut self, permissions: UserPermissions<[u32; 1]>) -> User {
|
pub fn with(mut self, permissions: UserPermissions<[u32; 1]>) -> User {
|
||||||
if permissions.get_view_profile() {
|
if permissions.get_view_profile() {
|
||||||
self.online = Some(is_online(&self.id));
|
self.online = Some(is_online(&self.id));
|
||||||
|
} else {
|
||||||
|
self.status = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.profile = None;
|
self.profile = None;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Mutate the user object to appear as seen by user.
|
||||||
|
/// Also overrides the relationship status.
|
||||||
|
pub async fn from_override(
|
||||||
|
mut self,
|
||||||
|
user: &User,
|
||||||
|
relationship: RelationshipStatus,
|
||||||
|
) -> Result<User> {
|
||||||
|
let permissions = PermissionCalculator::new(&user)
|
||||||
|
.with_relationship(&relationship)
|
||||||
|
.for_user(&self.id)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
self.relations = None;
|
||||||
|
self.relationship = Some(relationship);
|
||||||
|
Ok(self.with(permissions))
|
||||||
|
}
|
||||||
|
|
||||||
/// Utility function for checking claimed usernames.
|
/// Utility function for checking claimed usernames.
|
||||||
pub async fn is_username_taken(username: &str) -> Result<bool> {
|
pub async fn is_username_taken(username: &str) -> Result<bool> {
|
||||||
if username.to_lowercase() == "revolt" && username.to_lowercase() == "admin" {
|
if username.to_lowercase() == "revolt" && username.to_lowercase() == "admin" {
|
||||||
@@ -135,4 +164,46 @@ impl User {
|
|||||||
Ok(false)
|
Ok(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Utility function for fetching multiple users from the perspective of one.
|
||||||
|
pub async fn fetch_multiple_users(&self, user_ids: Vec<String>) -> Result<Vec<User>> {
|
||||||
|
let mut users = vec![];
|
||||||
|
let mut cursor = get_collection("users")
|
||||||
|
.find(
|
||||||
|
doc! {
|
||||||
|
"_id": {
|
||||||
|
"$in": user_ids
|
||||||
|
}
|
||||||
|
},
|
||||||
|
FindOptions::builder()
|
||||||
|
.projection(
|
||||||
|
doc! { "_id": 1, "username": 1, "avatar": 1, "badges": 1, "status": 1 },
|
||||||
|
)
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|_| Error::DatabaseError {
|
||||||
|
operation: "find",
|
||||||
|
with: "users",
|
||||||
|
})?;
|
||||||
|
|
||||||
|
while let Some(result) = cursor.next().await {
|
||||||
|
if let Ok(doc) = result {
|
||||||
|
let other: User = from_document(doc).map_err(|_| Error::DatabaseError {
|
||||||
|
operation: "from_document",
|
||||||
|
with: "user",
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let permissions = PermissionCalculator::new(&self)
|
||||||
|
.with_mutual_connection()
|
||||||
|
.with_user(&other)
|
||||||
|
.for_user_given()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
users.push(other.from(&self).with(permissions));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(users)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -25,10 +25,18 @@ pub async fn create_database() {
|
|||||||
.await
|
.await
|
||||||
.expect("Failed to create messages collection.");
|
.expect("Failed to create messages collection.");
|
||||||
|
|
||||||
|
db.create_collection("servers", None)
|
||||||
|
.await
|
||||||
|
.expect("Failed to create servers collection.");
|
||||||
|
|
||||||
db.create_collection("migrations", None)
|
db.create_collection("migrations", None)
|
||||||
.await
|
.await
|
||||||
.expect("Failed to create migrations collection.");
|
.expect("Failed to create migrations collection.");
|
||||||
|
|
||||||
|
db.create_collection("attachments", None)
|
||||||
|
.await
|
||||||
|
.expect("Failed to create attachments collection.");
|
||||||
|
|
||||||
db.create_collection(
|
db.create_collection(
|
||||||
"pubsub",
|
"pubsub",
|
||||||
CreateCollectionOptions::builder()
|
CreateCollectionOptions::builder()
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::database::get_collection;
|
use crate::database::{get_collection, get_db};
|
||||||
|
|
||||||
use log::info;
|
use log::info;
|
||||||
use mongodb::bson::{doc, from_document};
|
use mongodb::bson::{doc, from_document};
|
||||||
@@ -10,7 +10,7 @@ struct MigrationInfo {
|
|||||||
revision: i32,
|
revision: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const LATEST_REVISION: i32 = 0;
|
pub const LATEST_REVISION: i32 = 3;
|
||||||
|
|
||||||
pub async fn migrate_database() {
|
pub async fn migrate_database() {
|
||||||
let migrations = get_collection("migrations");
|
let migrations = get_collection("migrations");
|
||||||
@@ -53,6 +53,40 @@ pub async fn run_migrations(revision: i32) -> i32 {
|
|||||||
info!("Running migration [revision 0]: Test migration system.");
|
info!("Running migration [revision 0]: Test migration system.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if revision <= 1 {
|
||||||
|
info!("Running migration [revision 1 / 2021-04-24]: Migrate to Autumn v1.0.0.");
|
||||||
|
|
||||||
|
let messages = get_collection("messages");
|
||||||
|
let attachments = get_collection("attachments");
|
||||||
|
|
||||||
|
messages
|
||||||
|
.update_many(
|
||||||
|
doc! { "attachment": { "$exists": 1 } },
|
||||||
|
doc! { "$set": { "attachment.tag": "attachments", "attachment.size": 0 } },
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("Failed to update messages.");
|
||||||
|
|
||||||
|
attachments
|
||||||
|
.update_many(
|
||||||
|
doc! {},
|
||||||
|
doc! { "$set": { "tag": "attachments", "size": 0 } },
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("Failed to update attachments.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if revision <= 2 {
|
||||||
|
info!("Running migration [revision 2 / 2021-05-08]: Add servers collection.");
|
||||||
|
|
||||||
|
get_db()
|
||||||
|
.create_collection("servers", None)
|
||||||
|
.await
|
||||||
|
.expect("Failed to create servers collection.");
|
||||||
|
}
|
||||||
|
|
||||||
// Reminder to update LATEST_REVISION when adding new migrations.
|
// Reminder to update LATEST_REVISION when adding new migrations.
|
||||||
LATEST_REVISION
|
LATEST_REVISION
|
||||||
}
|
}
|
||||||
@@ -40,7 +40,7 @@ impl<'a> PermissionCalculator<'a> {
|
|||||||
match channel {
|
match channel {
|
||||||
Channel::SavedMessages { user: owner, .. } => {
|
Channel::SavedMessages { user: owner, .. } => {
|
||||||
if &self.perspective.id == owner {
|
if &self.perspective.id == owner {
|
||||||
Ok(u32::MAX)
|
Ok(u32::MAX - ChannelPermission::VoiceCall as u32)
|
||||||
} else {
|
} else {
|
||||||
Ok(0)
|
Ok(0)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ pub struct PermissionCalculator<'a> {
|
|||||||
perspective: &'a User,
|
perspective: &'a User,
|
||||||
|
|
||||||
user: Option<&'a User>,
|
user: Option<&'a User>,
|
||||||
|
relationship: Option<&'a RelationshipStatus>,
|
||||||
channel: Option<&'a Channel>,
|
channel: Option<&'a Channel>,
|
||||||
|
|
||||||
has_mutual_connection: bool,
|
has_mutual_connection: bool,
|
||||||
@@ -20,6 +21,7 @@ impl<'a> PermissionCalculator<'a> {
|
|||||||
perspective,
|
perspective,
|
||||||
|
|
||||||
user: None,
|
user: None,
|
||||||
|
relationship: None,
|
||||||
channel: None,
|
channel: None,
|
||||||
|
|
||||||
has_mutual_connection: false,
|
has_mutual_connection: false,
|
||||||
@@ -33,6 +35,13 @@ impl<'a> PermissionCalculator<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn with_relationship(self, relationship: &'a RelationshipStatus) -> PermissionCalculator {
|
||||||
|
PermissionCalculator {
|
||||||
|
relationship: Some(&relationship),
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn with_channel(self, channel: &'a Channel) -> PermissionCalculator {
|
pub fn with_channel(self, channel: &'a Channel) -> PermissionCalculator {
|
||||||
PermissionCalculator {
|
PermissionCalculator {
|
||||||
channel: Some(&channel),
|
channel: Some(&channel),
|
||||||
|
|||||||
@@ -49,7 +49,12 @@ impl<'a> PermissionCalculator<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut permissions: u32 = 0;
|
let mut permissions: u32 = 0;
|
||||||
match get_relationship(&self.perspective, &target) {
|
match self
|
||||||
|
.relationship
|
||||||
|
.clone()
|
||||||
|
.map(|v| v.to_owned())
|
||||||
|
.unwrap_or_else(|| get_relationship(&self.perspective, &target))
|
||||||
|
{
|
||||||
RelationshipStatus::Friend => return Ok(u32::MAX),
|
RelationshipStatus::Friend => return Ok(u32::MAX),
|
||||||
RelationshipStatus::Blocked | RelationshipStatus::BlockedOther => {
|
RelationshipStatus::Blocked | RelationshipStatus::BlockedOther => {
|
||||||
return Ok(UserPermission::Access as u32)
|
return Ok(UserPermission::Access as u32)
|
||||||
|
|||||||
+12
-4
@@ -17,7 +17,9 @@ pub mod database;
|
|||||||
pub mod notifications;
|
pub mod notifications;
|
||||||
pub mod routes;
|
pub mod routes;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
|
pub mod version;
|
||||||
|
|
||||||
|
use async_std::task;
|
||||||
use chrono::Duration;
|
use chrono::Duration;
|
||||||
use futures::join;
|
use futures::join;
|
||||||
use log::info;
|
use log::info;
|
||||||
@@ -38,7 +40,10 @@ async fn main() {
|
|||||||
dotenv::dotenv().ok();
|
dotenv::dotenv().ok();
|
||||||
env_logger::init_from_env(env_logger::Env::default().filter_or("RUST_LOG", "info"));
|
env_logger::init_from_env(env_logger::Env::default().filter_or("RUST_LOG", "info"));
|
||||||
|
|
||||||
info!("Starting REVOLT server.");
|
info!(
|
||||||
|
"Starting REVOLT server [version {}].",
|
||||||
|
crate::version::VERSION
|
||||||
|
);
|
||||||
|
|
||||||
util::variables::preflight_checks();
|
util::variables::preflight_checks();
|
||||||
database::connect().await;
|
database::connect().await;
|
||||||
@@ -50,10 +55,13 @@ async fn main() {
|
|||||||
})
|
})
|
||||||
.expect("Error setting Ctrl-C handler");
|
.expect("Error setting Ctrl-C handler");
|
||||||
|
|
||||||
|
let web_task = task::spawn(launch_web());
|
||||||
|
let hive_task = task::spawn(notifications::hive::listen());
|
||||||
|
|
||||||
join!(
|
join!(
|
||||||
launch_web(),
|
web_task,
|
||||||
notifications::websocket::launch_server(),
|
hive_task,
|
||||||
notifications::hive::listen(),
|
notifications::websocket::launch_server()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,19 @@ pub enum ServerboundNotification {
|
|||||||
EndTyping { channel: String },
|
EndTyping { channel: String },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub enum RemoveUserField {
|
||||||
|
ProfileContent,
|
||||||
|
ProfileBackground,
|
||||||
|
StatusText,
|
||||||
|
Avatar,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub enum RemoveChannelField {
|
||||||
|
Icon,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
#[serde(tag = "type")]
|
#[serde(tag = "type")]
|
||||||
pub enum ClientboundNotification {
|
pub enum ClientboundNotification {
|
||||||
@@ -53,6 +66,8 @@ pub enum ClientboundNotification {
|
|||||||
ChannelUpdate {
|
ChannelUpdate {
|
||||||
id: String,
|
id: String,
|
||||||
data: JsonValue,
|
data: JsonValue,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
clear: Option<RemoveChannelField>,
|
||||||
},
|
},
|
||||||
ChannelGroupJoin {
|
ChannelGroupJoin {
|
||||||
id: String,
|
id: String,
|
||||||
@@ -77,10 +92,12 @@ pub enum ClientboundNotification {
|
|||||||
UserUpdate {
|
UserUpdate {
|
||||||
id: String,
|
id: String,
|
||||||
data: JsonValue,
|
data: JsonValue,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
clear: Option<RemoveUserField>,
|
||||||
},
|
},
|
||||||
UserRelationship {
|
UserRelationship {
|
||||||
id: String,
|
id: String,
|
||||||
user: String,
|
user: User,
|
||||||
status: RelationshipStatus,
|
status: RelationshipStatus,
|
||||||
},
|
},
|
||||||
UserPresence {
|
UserPresence {
|
||||||
@@ -90,9 +107,13 @@ pub enum ClientboundNotification {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ClientboundNotification {
|
impl ClientboundNotification {
|
||||||
pub async fn publish(self, topic: String) -> Result<(), String> {
|
pub fn publish(self, topic: String) {
|
||||||
|
async_std::task::spawn(async move {
|
||||||
prehandle_hook(&self); // ! TODO: this should be moved to pubsub
|
prehandle_hook(&self); // ! TODO: this should be moved to pubsub
|
||||||
hive_pubsub::backend::mongo::publish(get_hive(), &topic, self).await
|
hive_pubsub::backend::mongo::publish(get_hive(), &topic, self)
|
||||||
|
.await
|
||||||
|
.ok();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,7 +137,7 @@ pub fn prehandle_hook(notification: &ClientboundNotification) {
|
|||||||
}
|
}
|
||||||
ClientboundNotification::UserRelationship { id, user, status } => {
|
ClientboundNotification::UserRelationship { id, user, status } => {
|
||||||
if status != &RelationshipStatus::None {
|
if status != &RelationshipStatus::None {
|
||||||
subscribe_if_exists(id.clone(), user.clone()).ok();
|
subscribe_if_exists(id.clone(), user.id.clone()).ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
@@ -132,7 +153,7 @@ pub fn posthandle_hook(notification: &ClientboundNotification) {
|
|||||||
if status == &RelationshipStatus::None {
|
if status == &RelationshipStatus::None {
|
||||||
get_hive()
|
get_hive()
|
||||||
.hive
|
.hive
|
||||||
.unsubscribe(&id.to_string(), &user.to_string())
|
.unsubscribe(&id.to_string(), &user.id.to_string())
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,13 +6,9 @@ use crate::{
|
|||||||
util::result::{Error, Result},
|
util::result::{Error, Result},
|
||||||
};
|
};
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use mongodb::{
|
use mongodb::bson::{doc, from_document};
|
||||||
bson::{doc, from_document},
|
|
||||||
options::FindOptions,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub async fn generate_ready(mut user: User) -> Result<ClientboundNotification> {
|
pub async fn generate_ready(mut user: User) -> Result<ClientboundNotification> {
|
||||||
let mut users = vec![];
|
|
||||||
let mut user_ids: HashSet<String> = HashSet::new();
|
let mut user_ids: HashSet<String> = HashSet::new();
|
||||||
|
|
||||||
if let Some(relationships) = &user.relations {
|
if let Some(relationships) = &user.relations {
|
||||||
@@ -68,41 +64,12 @@ pub async fn generate_ready(mut user: User) -> Result<ClientboundNotification> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
user_ids.remove(&user.id);
|
user_ids.remove(&user.id);
|
||||||
if user_ids.len() > 0 {
|
let mut users = if user_ids.len() > 0 {
|
||||||
let mut cursor = get_collection("users")
|
user.fetch_multiple_users(user_ids.into_iter().collect::<Vec<String>>())
|
||||||
.find(
|
.await?
|
||||||
doc! {
|
} else {
|
||||||
"_id": {
|
vec![]
|
||||||
"$in": user_ids.into_iter().collect::<Vec<String>>()
|
};
|
||||||
}
|
|
||||||
},
|
|
||||||
FindOptions::builder()
|
|
||||||
.projection(doc! { "_id": 1, "username": 1, "badges": 1, "status": 1 })
|
|
||||||
.build(),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.map_err(|_| Error::DatabaseError {
|
|
||||||
operation: "find",
|
|
||||||
with: "users",
|
|
||||||
})?;
|
|
||||||
|
|
||||||
while let Some(result) = cursor.next().await {
|
|
||||||
if let Ok(doc) = result {
|
|
||||||
let other: User = from_document(doc).map_err(|_| Error::DatabaseError {
|
|
||||||
operation: "from_document",
|
|
||||||
with: "user",
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let permissions = PermissionCalculator::new(&user)
|
|
||||||
.with_mutual_connection()
|
|
||||||
.with_user(&other)
|
|
||||||
.for_user_given()
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
users.push(other.from(&user).with(permissions));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
user.relationship = Some(RelationshipStatus::User);
|
user.relationship = Some(RelationshipStatus::User);
|
||||||
user.online = Some(true);
|
user.online = Some(true);
|
||||||
|
|||||||
@@ -133,9 +133,7 @@ async fn accept(stream: TcpStream) {
|
|||||||
id: id.clone(),
|
id: id.clone(),
|
||||||
online: true,
|
online: true,
|
||||||
}
|
}
|
||||||
.publish(id)
|
.publish(id);
|
||||||
.await
|
|
||||||
.ok();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
@@ -173,9 +171,7 @@ async fn accept(stream: TcpStream) {
|
|||||||
id: channel.clone(),
|
id: channel.clone(),
|
||||||
user,
|
user,
|
||||||
}
|
}
|
||||||
.publish(channel)
|
.publish(channel);
|
||||||
.await
|
|
||||||
.ok();
|
|
||||||
} else {
|
} else {
|
||||||
send(ClientboundNotification::Error(
|
send(ClientboundNotification::Error(
|
||||||
WebSocketError::AlreadyAuthenticated,
|
WebSocketError::AlreadyAuthenticated,
|
||||||
@@ -196,9 +192,7 @@ async fn accept(stream: TcpStream) {
|
|||||||
id: channel.clone(),
|
id: channel.clone(),
|
||||||
user,
|
user,
|
||||||
}
|
}
|
||||||
.publish(channel)
|
.publish(channel);
|
||||||
.await
|
|
||||||
.ok();
|
|
||||||
} else {
|
} else {
|
||||||
send(ClientboundNotification::Error(
|
send(ClientboundNotification::Error(
|
||||||
WebSocketError::AlreadyAuthenticated,
|
WebSocketError::AlreadyAuthenticated,
|
||||||
@@ -220,16 +214,28 @@ async fn accept(stream: TcpStream) {
|
|||||||
info!("User {} disconnected.", &addr);
|
info!("User {} disconnected.", &addr);
|
||||||
CONNECTIONS.lock().unwrap().remove(&addr);
|
CONNECTIONS.lock().unwrap().remove(&addr);
|
||||||
|
|
||||||
|
let mut offline = None;
|
||||||
|
{
|
||||||
let session = session.lock().unwrap();
|
let session = session.lock().unwrap();
|
||||||
if let Some(session) = session.as_ref() {
|
if let Some(session) = session.as_ref() {
|
||||||
let mut users = USERS.write().unwrap();
|
let mut users = USERS.write().unwrap();
|
||||||
users.remove(&session.user_id, &addr);
|
users.remove(&session.user_id, &addr);
|
||||||
if users.get_left(&session.user_id).is_none() {
|
if users.get_left(&session.user_id).is_none() {
|
||||||
get_hive().drop_client(&session.user_id).unwrap();
|
get_hive().drop_client(&session.user_id).unwrap();
|
||||||
|
offline = Some(session.user_id.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(id) = offline {
|
||||||
|
ClientboundNotification::UserPresence {
|
||||||
|
id: id.clone(),
|
||||||
|
online: false,
|
||||||
|
}
|
||||||
|
.publish(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn publish(ids: Vec<String>, notification: ClientboundNotification) {
|
pub fn publish(ids: Vec<String>, notification: ClientboundNotification) {
|
||||||
let mut targets = vec![];
|
let mut targets = vec![];
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -95,9 +95,7 @@ pub async fn req(user: User, target: Ref) -> Result<()> {
|
|||||||
id: id.clone(),
|
id: id.clone(),
|
||||||
user: user.id.clone(),
|
user: user.id.clone(),
|
||||||
}
|
}
|
||||||
.publish(id.clone())
|
.publish(id.clone());
|
||||||
.await
|
|
||||||
.ok();
|
|
||||||
|
|
||||||
Message::create(
|
Message::create(
|
||||||
"00000000000000000000000000".to_string(),
|
"00000000000000000000000000".to_string(),
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use crate::database::*;
|
|
||||||
use crate::notifications::events::ClientboundNotification;
|
use crate::notifications::events::ClientboundNotification;
|
||||||
use crate::util::result::{Error, Result};
|
use crate::util::result::{Error, Result};
|
||||||
|
use crate::{database::*, notifications::events::RemoveChannelField};
|
||||||
|
|
||||||
use mongodb::bson::{doc, to_document};
|
use mongodb::bson::{doc, to_document};
|
||||||
use rocket_contrib::json::Json;
|
use rocket_contrib::json::Json;
|
||||||
@@ -15,14 +15,22 @@ pub struct Data {
|
|||||||
#[validate(length(min = 0, max = 1024))]
|
#[validate(length(min = 0, max = 1024))]
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
description: Option<String>,
|
description: Option<String>,
|
||||||
|
#[validate(length(min = 1, max = 128))]
|
||||||
|
icon: Option<String>,
|
||||||
|
remove: Option<RemoveChannelField>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[patch("/<target>", data = "<info>")]
|
#[patch("/<target>", data = "<data>")]
|
||||||
pub async fn req(user: User, target: Ref, info: Json<Data>) -> Result<()> {
|
pub async fn req(user: User, target: Ref, data: Json<Data>) -> Result<()> {
|
||||||
info.validate()
|
let data = data.into_inner();
|
||||||
|
data.validate()
|
||||||
.map_err(|error| Error::FailedValidation { error })?;
|
.map_err(|error| Error::FailedValidation { error })?;
|
||||||
|
|
||||||
if info.name.is_none() && info.description.is_none() {
|
if data.name.is_none()
|
||||||
|
&& data.description.is_none()
|
||||||
|
&& data.icon.is_none()
|
||||||
|
&& data.remove.is_none()
|
||||||
|
{
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,38 +45,107 @@ pub async fn req(user: User, target: Ref, info: Json<Data>) -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
match &target {
|
match &target {
|
||||||
Channel::Group { id, .. } => {
|
Channel::Group { id, icon, .. } => {
|
||||||
|
let mut set = doc! {};
|
||||||
|
let mut unset = doc! {};
|
||||||
|
|
||||||
|
let mut remove_icon = false;
|
||||||
|
if let Some(remove) = &data.remove {
|
||||||
|
match remove {
|
||||||
|
RemoveChannelField::Icon => {
|
||||||
|
unset.insert("icon", 1);
|
||||||
|
remove_icon = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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", &user.id).await?;
|
||||||
|
set.insert(
|
||||||
|
"icon",
|
||||||
|
to_document(&attachment).map_err(|_| Error::DatabaseError {
|
||||||
|
operation: "to_document",
|
||||||
|
with: "attachment",
|
||||||
|
})?,
|
||||||
|
);
|
||||||
|
|
||||||
|
remove_icon = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut operations = doc! {};
|
||||||
|
if set.len() > 0 {
|
||||||
|
operations.insert("$set", &set);
|
||||||
|
}
|
||||||
|
|
||||||
|
if unset.len() > 0 {
|
||||||
|
operations.insert("$unset", unset);
|
||||||
|
}
|
||||||
|
|
||||||
|
if operations.len() > 0 {
|
||||||
get_collection("channels")
|
get_collection("channels")
|
||||||
.update_one(
|
.update_one(doc! { "_id": &id }, operations, None)
|
||||||
doc! { "_id": &id },
|
|
||||||
doc! { "$set": to_document(&info.0).map_err(|_| Error::DatabaseError { operation: "to_document", with: "data" })? },
|
|
||||||
None
|
|
||||||
)
|
|
||||||
.await
|
.await
|
||||||
.map_err(|_| Error::DatabaseError { operation: "update_one", with: "channel" })?;
|
.map_err(|_| Error::DatabaseError {
|
||||||
|
operation: "update_one",
|
||||||
|
with: "channel",
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
|
||||||
ClientboundNotification::ChannelUpdate {
|
ClientboundNotification::ChannelUpdate {
|
||||||
id: id.clone(),
|
id: id.clone(),
|
||||||
data: json!(info.0),
|
data: json!(set),
|
||||||
|
clear: data.remove,
|
||||||
}
|
}
|
||||||
.publish(id.clone())
|
.publish(id.clone());
|
||||||
.await
|
|
||||||
.ok();
|
|
||||||
|
|
||||||
if let Some(name) = &info.name {
|
if let Some(name) = data.name {
|
||||||
Message::create(
|
Message::create(
|
||||||
"00000000000000000000000000".to_string(),
|
"00000000000000000000000000".to_string(),
|
||||||
id.clone(),
|
id.clone(),
|
||||||
Content::SystemMessage(SystemMessage::ChannelRenamed {
|
Content::SystemMessage(SystemMessage::ChannelRenamed { name, by: user.id.clone() }),
|
||||||
name: name.clone(),
|
|
||||||
by: user.id,
|
|
||||||
}),
|
|
||||||
)
|
)
|
||||||
.publish(&target)
|
.publish(&target)
|
||||||
.await
|
.await
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(_) = data.description {
|
||||||
|
Message::create(
|
||||||
|
"00000000000000000000000000".to_string(),
|
||||||
|
id.clone(),
|
||||||
|
Content::SystemMessage(SystemMessage::ChannelDescriptionChanged { by: user.id.clone() }),
|
||||||
|
)
|
||||||
|
.publish(&target)
|
||||||
|
.await
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(_) = data.icon {
|
||||||
|
Message::create(
|
||||||
|
"00000000000000000000000000".to_string(),
|
||||||
|
id.clone(),
|
||||||
|
Content::SystemMessage(SystemMessage::ChannelIconChanged { by: user.id }),
|
||||||
|
)
|
||||||
|
.publish(&target)
|
||||||
|
.await
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
if remove_icon {
|
||||||
|
if let Some(old_icon) = icon {
|
||||||
|
old_icon.delete().await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
_ => Err(Error::InvalidOperation),
|
_ => Err(Error::InvalidOperation),
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -52,9 +52,7 @@ 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(
|
Message::create(
|
||||||
"00000000000000000000000000".to_string(),
|
"00000000000000000000000000".to_string(),
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ pub async fn req(user: User, info: Json<Data>) -> Result<JsonValue> {
|
|||||||
.unwrap_or_else(|| "A group.".to_string()),
|
.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,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -49,9 +49,7 @@ 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(
|
Message::create(
|
||||||
"00000000000000000000000000".to_string(),
|
"00000000000000000000000000".to_string(),
|
||||||
|
|||||||
@@ -11,6 +11,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 +25,7 @@ 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>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/<target>/messages?<options..>")]
|
#[get("/<target>/messages?<options..>")]
|
||||||
@@ -47,13 +54,18 @@ pub async fn req(user: User, target: Ref, options: Form<Options>) -> Result<Json
|
|||||||
query.insert("_id", doc! { "$gt": after });
|
query.insert("_id", doc! { "$gt": after });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let sort = if let Sort::Latest = options.sort.as_ref().unwrap_or_else(|| &Sort::Latest) {
|
||||||
|
-1
|
||||||
|
} else {
|
||||||
|
1
|
||||||
|
};
|
||||||
let mut cursor = get_collection("messages")
|
let mut cursor = get_collection("messages")
|
||||||
.find(
|
.find(
|
||||||
query,
|
query,
|
||||||
FindOptions::builder()
|
FindOptions::builder()
|
||||||
.limit(options.limit.unwrap_or(50))
|
.limit(options.limit.unwrap_or(50))
|
||||||
.sort(doc! {
|
.sort(doc! {
|
||||||
"_id": -1
|
"_id": sort
|
||||||
})
|
})
|
||||||
.build(),
|
.build(),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
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},
|
|
||||||
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;
|
||||||
@@ -61,51 +58,8 @@ 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 {
|
let attachment = if let Some(attachment_id) = &message.attachment {
|
||||||
if let Some(doc) = attachments
|
Some(File::find_and_use(attachment_id, "attachments", "message", &id).await?)
|
||||||
.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",
|
|
||||||
})?;
|
|
||||||
|
|
||||||
attachments
|
|
||||||
.update_one(
|
|
||||||
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 {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ use rocket::Route;
|
|||||||
mod delete_channel;
|
mod delete_channel;
|
||||||
mod edit_channel;
|
mod edit_channel;
|
||||||
mod fetch_channel;
|
mod fetch_channel;
|
||||||
|
mod fetch_members;
|
||||||
mod group_add_member;
|
mod group_add_member;
|
||||||
mod group_create;
|
mod group_create;
|
||||||
mod group_remove_member;
|
mod group_remove_member;
|
||||||
@@ -17,6 +18,7 @@ mod message_send;
|
|||||||
pub fn routes() -> Vec<Route> {
|
pub fn routes() -> Vec<Route> {
|
||||||
routes![
|
routes![
|
||||||
fetch_channel::req,
|
fetch_channel::req,
|
||||||
|
fetch_members::req,
|
||||||
delete_channel::req,
|
delete_channel::req,
|
||||||
edit_channel::req,
|
edit_channel::req,
|
||||||
message_send::req,
|
message_send::req,
|
||||||
|
|||||||
+7
-3
@@ -1,6 +1,6 @@
|
|||||||
use crate::util::variables::{
|
use crate::util::variables::{
|
||||||
APP_URL, AUTUMN_URL, DISABLE_REGISTRATION, EXTERNAL_WS_URL, HCAPTCHA_SITEKEY, INVITE_ONLY,
|
APP_URL, JANUARY_URL, AUTUMN_URL, DISABLE_REGISTRATION, EXTERNAL_WS_URL, HCAPTCHA_SITEKEY, INVITE_ONLY,
|
||||||
USE_AUTUMN, USE_EMAIL, USE_HCAPTCHA, USE_VOSO, VAPID_PUBLIC_KEY, VOSO_URL, VOSO_WS_HOST,
|
USE_AUTUMN, USE_JANUARY, USE_EMAIL, USE_HCAPTCHA, USE_VOSO, VAPID_PUBLIC_KEY, VOSO_URL, VOSO_WS_HOST,
|
||||||
};
|
};
|
||||||
|
|
||||||
use mongodb::bson::doc;
|
use mongodb::bson::doc;
|
||||||
@@ -9,7 +9,7 @@ use rocket_contrib::json::JsonValue;
|
|||||||
#[get("/")]
|
#[get("/")]
|
||||||
pub async fn root() -> JsonValue {
|
pub async fn root() -> JsonValue {
|
||||||
json!({
|
json!({
|
||||||
"revolt": "0.4.0-alpha.6",
|
"revolt": crate::version::VERSION,
|
||||||
"features": {
|
"features": {
|
||||||
"registration": !*DISABLE_REGISTRATION,
|
"registration": !*DISABLE_REGISTRATION,
|
||||||
"captcha": {
|
"captcha": {
|
||||||
@@ -22,6 +22,10 @@ pub async fn root() -> JsonValue {
|
|||||||
"enabled": *USE_AUTUMN,
|
"enabled": *USE_AUTUMN,
|
||||||
"url": *AUTUMN_URL
|
"url": *AUTUMN_URL
|
||||||
},
|
},
|
||||||
|
"january": {
|
||||||
|
"enabled": *USE_JANUARY,
|
||||||
|
"url": *JANUARY_URL
|
||||||
|
},
|
||||||
"voso": {
|
"voso": {
|
||||||
"enabled": *USE_VOSO,
|
"enabled": *USE_VOSO,
|
||||||
"url": *VOSO_URL,
|
"url": *VOSO_URL,
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ pub async fn req(user: User, username: String) -> Result<JsonValue> {
|
|||||||
with: "user",
|
with: "user",
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
let target_user = Ref::from(target_id.to_string())?.fetch_user().await?;
|
||||||
|
|
||||||
match get_relationship(&user, &target_id) {
|
match get_relationship(&user, &target_id) {
|
||||||
RelationshipStatus::User => return Err(Error::NoEffect),
|
RelationshipStatus::User => return Err(Error::NoEffect),
|
||||||
RelationshipStatus::Friend => return Err(Error::AlreadyFriends),
|
RelationshipStatus::Friend => return Err(Error::AlreadyFriends),
|
||||||
@@ -65,21 +67,26 @@ pub async fn req(user: User, username: String) -> Result<JsonValue> {
|
|||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
try_join!(
|
let target_user = target_user
|
||||||
|
.from_override(&user, RelationshipStatus::Friend)
|
||||||
|
.await?;
|
||||||
|
let user = user
|
||||||
|
.from_override(&target_user, RelationshipStatus::Friend)
|
||||||
|
.await?;
|
||||||
|
|
||||||
ClientboundNotification::UserRelationship {
|
ClientboundNotification::UserRelationship {
|
||||||
id: user.id.clone(),
|
id: user.id.clone(),
|
||||||
user: target_id.to_string(),
|
user: target_user,
|
||||||
status: RelationshipStatus::Friend
|
status: RelationshipStatus::Friend,
|
||||||
}
|
}
|
||||||
.publish(user.id.clone()),
|
.publish(user.id.clone());
|
||||||
|
|
||||||
ClientboundNotification::UserRelationship {
|
ClientboundNotification::UserRelationship {
|
||||||
id: target_id.to_string(),
|
id: target_id.to_string(),
|
||||||
user: user.id.clone(),
|
user,
|
||||||
status: RelationshipStatus::Friend
|
status: RelationshipStatus::Friend,
|
||||||
}
|
}
|
||||||
.publish(target_id.to_string())
|
.publish(target_id.to_string());
|
||||||
)
|
|
||||||
.ok();
|
|
||||||
|
|
||||||
Ok(json!({ "status": "Friend" }))
|
Ok(json!({ "status": "Friend" }))
|
||||||
}
|
}
|
||||||
@@ -121,21 +128,26 @@ pub async fn req(user: User, username: String) -> Result<JsonValue> {
|
|||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
try_join!(
|
let target_user = target_user
|
||||||
|
.from_override(&user, RelationshipStatus::Outgoing)
|
||||||
|
.await?;
|
||||||
|
let user = user
|
||||||
|
.from_override(&target_user, RelationshipStatus::Incoming)
|
||||||
|
.await?;
|
||||||
|
|
||||||
ClientboundNotification::UserRelationship {
|
ClientboundNotification::UserRelationship {
|
||||||
id: user.id.clone(),
|
id: user.id.clone(),
|
||||||
user: target_id.to_string(),
|
user: target_user,
|
||||||
status: RelationshipStatus::Outgoing
|
status: RelationshipStatus::Outgoing,
|
||||||
}
|
}
|
||||||
.publish(user.id.clone()),
|
.publish(user.id.clone());
|
||||||
|
|
||||||
ClientboundNotification::UserRelationship {
|
ClientboundNotification::UserRelationship {
|
||||||
id: target_id.to_string(),
|
id: target_id.to_string(),
|
||||||
user: user.id.clone(),
|
user,
|
||||||
status: RelationshipStatus::Incoming
|
status: RelationshipStatus::Incoming,
|
||||||
}
|
}
|
||||||
.publish(target_id.to_string())
|
.publish(target_id.to_string());
|
||||||
)
|
|
||||||
.ok();
|
|
||||||
|
|
||||||
Ok(json!({ "status": "Outgoing" }))
|
Ok(json!({ "status": "Outgoing" }))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ use rocket_contrib::json::JsonValue;
|
|||||||
pub async fn req(user: User, target: Ref) -> Result<JsonValue> {
|
pub async fn req(user: User, target: Ref) -> Result<JsonValue> {
|
||||||
let col = get_collection("users");
|
let col = get_collection("users");
|
||||||
|
|
||||||
|
let target = target.fetch_user().await?;
|
||||||
|
|
||||||
match get_relationship(&user, &target.id) {
|
match get_relationship(&user, &target.id) {
|
||||||
RelationshipStatus::User | RelationshipStatus::Blocked => Err(Error::NoEffect),
|
RelationshipStatus::User | RelationshipStatus::Blocked => Err(Error::NoEffect),
|
||||||
RelationshipStatus::BlockedOther => {
|
RelationshipStatus::BlockedOther => {
|
||||||
@@ -33,12 +35,10 @@ pub async fn req(user: User, target: Ref) -> Result<JsonValue> {
|
|||||||
|
|
||||||
ClientboundNotification::UserRelationship {
|
ClientboundNotification::UserRelationship {
|
||||||
id: user.id.clone(),
|
id: user.id.clone(),
|
||||||
user: target.id.clone(),
|
user: target,
|
||||||
status: RelationshipStatus::Blocked,
|
status: RelationshipStatus::Blocked,
|
||||||
}
|
}
|
||||||
.publish(user.id.clone())
|
.publish(user.id.clone());
|
||||||
.await
|
|
||||||
.ok();
|
|
||||||
|
|
||||||
Ok(json!({ "status": "Blocked" }))
|
Ok(json!({ "status": "Blocked" }))
|
||||||
}
|
}
|
||||||
@@ -74,21 +74,27 @@ pub async fn req(user: User, target: Ref) -> Result<JsonValue> {
|
|||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
try_join!(
|
let target = target
|
||||||
|
.from_override(&user, RelationshipStatus::Blocked)
|
||||||
|
.await?;
|
||||||
|
let user = user
|
||||||
|
.from_override(&target, RelationshipStatus::BlockedOther)
|
||||||
|
.await?;
|
||||||
|
let target_id = target.id.clone();
|
||||||
|
|
||||||
ClientboundNotification::UserRelationship {
|
ClientboundNotification::UserRelationship {
|
||||||
id: user.id.clone(),
|
id: user.id.clone(),
|
||||||
user: target.id.clone(),
|
user: target,
|
||||||
status: RelationshipStatus::Blocked
|
status: RelationshipStatus::Blocked,
|
||||||
}
|
}
|
||||||
.publish(user.id.clone()),
|
.publish(user.id.clone());
|
||||||
|
|
||||||
ClientboundNotification::UserRelationship {
|
ClientboundNotification::UserRelationship {
|
||||||
id: target.id.clone(),
|
id: target_id.clone(),
|
||||||
user: user.id.clone(),
|
user,
|
||||||
status: RelationshipStatus::BlockedOther
|
status: RelationshipStatus::BlockedOther,
|
||||||
}
|
}
|
||||||
.publish(target.id.clone())
|
.publish(target_id);
|
||||||
)
|
|
||||||
.ok();
|
|
||||||
|
|
||||||
Ok(json!({ "status": "Blocked" }))
|
Ok(json!({ "status": "Blocked" }))
|
||||||
}
|
}
|
||||||
@@ -128,21 +134,27 @@ pub async fn req(user: User, target: Ref) -> Result<JsonValue> {
|
|||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
try_join!(
|
let target = target
|
||||||
|
.from_override(&user, RelationshipStatus::Blocked)
|
||||||
|
.await?;
|
||||||
|
let user = user
|
||||||
|
.from_override(&target, RelationshipStatus::BlockedOther)
|
||||||
|
.await?;
|
||||||
|
let target_id = target.id.clone();
|
||||||
|
|
||||||
ClientboundNotification::UserRelationship {
|
ClientboundNotification::UserRelationship {
|
||||||
id: user.id.clone(),
|
id: user.id.clone(),
|
||||||
user: target.id.clone(),
|
user: target,
|
||||||
status: RelationshipStatus::Blocked
|
status: RelationshipStatus::Blocked,
|
||||||
}
|
}
|
||||||
.publish(user.id.clone()),
|
.publish(user.id.clone());
|
||||||
|
|
||||||
ClientboundNotification::UserRelationship {
|
ClientboundNotification::UserRelationship {
|
||||||
id: target.id.clone(),
|
id: target_id.clone(),
|
||||||
user: user.id.clone(),
|
user,
|
||||||
status: RelationshipStatus::BlockedOther
|
status: RelationshipStatus::BlockedOther,
|
||||||
}
|
}
|
||||||
.publish(target.id.clone())
|
.publish(target_id);
|
||||||
)
|
|
||||||
.ok();
|
|
||||||
|
|
||||||
Ok(json!({ "status": "Blocked" }))
|
Ok(json!({ "status": "Blocked" }))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,10 +58,9 @@ pub async fn req(
|
|||||||
ClientboundNotification::UserUpdate {
|
ClientboundNotification::UserUpdate {
|
||||||
id: user.id.clone(),
|
id: user.id.clone(),
|
||||||
data: json!(data.0),
|
data: json!(data.0),
|
||||||
|
clear: None,
|
||||||
}
|
}
|
||||||
.publish(user.id.clone())
|
.publish(user.id.clone());
|
||||||
.await
|
|
||||||
.ok();
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
+154
-16
@@ -1,41 +1,179 @@
|
|||||||
use crate::database::*;
|
|
||||||
use crate::notifications::events::ClientboundNotification;
|
use crate::notifications::events::ClientboundNotification;
|
||||||
use crate::util::result::{Error, Result};
|
use crate::util::result::{Error, Result};
|
||||||
|
use crate::{database::*, notifications::events::RemoveUserField};
|
||||||
|
|
||||||
use mongodb::bson::{doc, to_document};
|
use mongodb::bson::{doc, to_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;
|
||||||
|
|
||||||
|
#[derive(Validate, Serialize, Deserialize, Debug)]
|
||||||
|
pub struct UserProfileData {
|
||||||
|
#[validate(length(min = 0, max = 2000))]
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
content: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
#[validate(length(min = 1, max = 128))]
|
||||||
|
background: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Validate, Serialize, Deserialize)]
|
#[derive(Validate, Serialize, Deserialize)]
|
||||||
pub struct Data {
|
pub struct Data {
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[validate]
|
||||||
status: Option<UserStatus>,
|
status: Option<UserStatus>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[validate]
|
||||||
profile: Option<UserProfile>,
|
profile: Option<UserProfileData>,
|
||||||
|
#[validate(length(min = 1, max = 128))]
|
||||||
|
avatar: Option<String>,
|
||||||
|
remove: Option<RemoveUserField>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[patch("/<_ignore_id>", data = "<data>")]
|
#[patch("/<_ignore_id>", data = "<data>")]
|
||||||
pub async fn req(user: User, data: Json<Data>, _ignore_id: String) -> Result<()> {
|
pub async fn req(user: User, data: Json<Data>, _ignore_id: String) -> Result<()> {
|
||||||
|
let mut data = data.into_inner();
|
||||||
|
|
||||||
data.validate()
|
data.validate()
|
||||||
.map_err(|error| Error::FailedValidation { error })?;
|
.map_err(|error| Error::FailedValidation { error })?;
|
||||||
|
|
||||||
get_collection("users")
|
if data.status.is_none()
|
||||||
.update_one(
|
&& data.profile.is_none()
|
||||||
doc! { "_id": &user.id },
|
&& data.avatar.is_none()
|
||||||
doc! { "$set": to_document(&data.0).map_err(|_| Error::DatabaseError { operation: "to_document", with: "data" })? },
|
&& data.remove.is_none()
|
||||||
None
|
{
|
||||||
)
|
return Ok(());
|
||||||
.await
|
}
|
||||||
.map_err(|_| Error::DatabaseError { operation: "update_one", with: "user" })?;
|
|
||||||
|
|
||||||
|
let mut unset = doc! {};
|
||||||
|
let mut set = doc! {};
|
||||||
|
|
||||||
|
let mut remove_background = false;
|
||||||
|
let mut remove_avatar = false;
|
||||||
|
|
||||||
|
if let Some(remove) = &data.remove {
|
||||||
|
match remove {
|
||||||
|
RemoveUserField::ProfileContent => {
|
||||||
|
unset.insert("profile.content", 1);
|
||||||
|
}
|
||||||
|
RemoveUserField::ProfileBackground => {
|
||||||
|
unset.insert("profile.background", 1);
|
||||||
|
remove_background = true;
|
||||||
|
}
|
||||||
|
RemoveUserField::StatusText => {
|
||||||
|
unset.insert("status.text", 1);
|
||||||
|
}
|
||||||
|
RemoveUserField::Avatar => {
|
||||||
|
unset.insert("avatar", 1);
|
||||||
|
remove_avatar = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(status) = &data.status {
|
||||||
|
set.insert(
|
||||||
|
"status",
|
||||||
|
to_document(&status).map_err(|_| Error::DatabaseError {
|
||||||
|
operation: "to_document",
|
||||||
|
with: "status",
|
||||||
|
})?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(profile) = data.profile {
|
||||||
|
if let Some(content) = profile.content {
|
||||||
|
set.insert("profile.content", content);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(attachment_id) = profile.background {
|
||||||
|
let attachment =
|
||||||
|
File::find_and_use(&attachment_id, "backgrounds", "user", &user.id).await?;
|
||||||
|
set.insert(
|
||||||
|
"profile.background",
|
||||||
|
to_document(&attachment).map_err(|_| Error::DatabaseError {
|
||||||
|
operation: "to_document",
|
||||||
|
with: "attachment",
|
||||||
|
})?,
|
||||||
|
);
|
||||||
|
|
||||||
|
remove_background = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let avatar = std::mem::replace(&mut data.avatar, None);
|
||||||
|
let attachment = if let Some(attachment_id) = avatar {
|
||||||
|
let attachment = File::find_and_use(&attachment_id, "avatars", "user", &user.id).await?;
|
||||||
|
set.insert(
|
||||||
|
"avatar",
|
||||||
|
to_document(&attachment).map_err(|_| Error::DatabaseError {
|
||||||
|
operation: "to_document",
|
||||||
|
with: "attachment",
|
||||||
|
})?,
|
||||||
|
);
|
||||||
|
|
||||||
|
remove_avatar = true;
|
||||||
|
Some(attachment)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut operations = doc! {};
|
||||||
|
if set.len() > 0 {
|
||||||
|
operations.insert("$set", set);
|
||||||
|
}
|
||||||
|
|
||||||
|
if unset.len() > 0 {
|
||||||
|
operations.insert("$unset", unset);
|
||||||
|
}
|
||||||
|
|
||||||
|
if operations.len() > 0 {
|
||||||
|
get_collection("users")
|
||||||
|
.update_one(doc! { "_id": &user.id }, operations, None)
|
||||||
|
.await
|
||||||
|
.map_err(|_| Error::DatabaseError {
|
||||||
|
operation: "update_one",
|
||||||
|
with: "user",
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(status) = &data.status {
|
||||||
ClientboundNotification::UserUpdate {
|
ClientboundNotification::UserUpdate {
|
||||||
id: user.id.clone(),
|
id: user.id.clone(),
|
||||||
data: json!(data.0),
|
data: json!({ "status": status }),
|
||||||
|
clear: None,
|
||||||
|
}
|
||||||
|
.publish(user.id.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(avatar) = attachment {
|
||||||
|
ClientboundNotification::UserUpdate {
|
||||||
|
id: user.id.clone(),
|
||||||
|
data: json!({ "avatar": avatar }),
|
||||||
|
clear: None,
|
||||||
|
}
|
||||||
|
.publish(user.id.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(clear) = data.remove {
|
||||||
|
ClientboundNotification::UserUpdate {
|
||||||
|
id: user.id.clone(),
|
||||||
|
data: json!({}),
|
||||||
|
clear: Some(clear),
|
||||||
|
}
|
||||||
|
.publish(user.id.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
if remove_avatar {
|
||||||
|
if let Some(old_avatar) = user.avatar {
|
||||||
|
old_avatar.delete().await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if remove_background {
|
||||||
|
if let Some(profile) = user.profile {
|
||||||
|
if let Some(old_background) = profile.background {
|
||||||
|
old_background.delete().await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.publish(user.id.clone())
|
|
||||||
.await
|
|
||||||
.ok();
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -12,8 +12,8 @@ pub async fn req(user: User, target: Ref) -> Result<JsonValue> {
|
|||||||
.find(
|
.find(
|
||||||
doc! {
|
doc! {
|
||||||
"$and": [
|
"$and": [
|
||||||
{ "relations._id": &user.id, "relations.status": "Friend" },
|
{ "relations": { "$elemMatch": { "_id": &user.id, "status": "Friend" } } },
|
||||||
{ "relations._id": &target.id, "relations.status": "Friend" }
|
{ "relations": { "$elemMatch": { "_id": &target.id, "status": "Friend" } } }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
FindOptions::builder().projection(doc! { "_id": 1 }).build(),
|
FindOptions::builder().projection(doc! { "_id": 1 }).build(),
|
||||||
|
|||||||
@@ -1,49 +0,0 @@
|
|||||||
use md5;
|
|
||||||
use mongodb::bson::doc;
|
|
||||||
use mongodb::options::FindOneOptions;
|
|
||||||
use rocket::response::Redirect;
|
|
||||||
use urlencoding;
|
|
||||||
|
|
||||||
use crate::database::*;
|
|
||||||
use crate::util::result::{Error, Result};
|
|
||||||
use crate::util::variables::PUBLIC_URL;
|
|
||||||
|
|
||||||
#[get("/<target>/avatar")]
|
|
||||||
pub async fn req(target: Ref) -> Result<Redirect> {
|
|
||||||
let doc = get_collection("accounts")
|
|
||||||
.find_one(
|
|
||||||
doc! {
|
|
||||||
"_id": &target.id
|
|
||||||
},
|
|
||||||
FindOneOptions::builder()
|
|
||||||
.projection(doc! { "email": 1 })
|
|
||||||
.build(),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.map_err(|_| Error::DatabaseError {
|
|
||||||
operation: "find_one",
|
|
||||||
with: "user",
|
|
||||||
})?
|
|
||||||
.ok_or_else(|| Error::UnknownUser)?;
|
|
||||||
|
|
||||||
let email = doc
|
|
||||||
.get_str("email")
|
|
||||||
.map_err(|_| Error::DatabaseError {
|
|
||||||
operation: "get_str(email)",
|
|
||||||
with: "user",
|
|
||||||
})?
|
|
||||||
.to_lowercase();
|
|
||||||
|
|
||||||
let url = format!(
|
|
||||||
"https://www.gravatar.com/avatar/{:x}?s=128&d={}",
|
|
||||||
md5::compute(email),
|
|
||||||
urlencoding::encode(&format!(
|
|
||||||
"{}/users/{}/default_avatar",
|
|
||||||
*PUBLIC_URL, &target.id
|
|
||||||
))
|
|
||||||
);
|
|
||||||
|
|
||||||
dbg!(&url);
|
|
||||||
|
|
||||||
Ok(Redirect::to(url))
|
|
||||||
}
|
|
||||||
@@ -10,7 +10,6 @@ mod fetch_relationship;
|
|||||||
mod fetch_relationships;
|
mod fetch_relationships;
|
||||||
mod fetch_user;
|
mod fetch_user;
|
||||||
mod find_mutual;
|
mod find_mutual;
|
||||||
mod get_avatar;
|
|
||||||
mod get_default_avatar;
|
mod get_default_avatar;
|
||||||
mod open_dm;
|
mod open_dm;
|
||||||
mod remove_friend;
|
mod remove_friend;
|
||||||
@@ -23,7 +22,6 @@ pub fn routes() -> Vec<Route> {
|
|||||||
edit_user::req,
|
edit_user::req,
|
||||||
change_username::req,
|
change_username::req,
|
||||||
get_default_avatar::req,
|
get_default_avatar::req,
|
||||||
get_avatar::req,
|
|
||||||
fetch_profile::req,
|
fetch_profile::req,
|
||||||
// Direct Messaging
|
// Direct Messaging
|
||||||
fetch_dms::req,
|
fetch_dms::req,
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ use rocket_contrib::json::JsonValue;
|
|||||||
pub async fn req(user: User, target: Ref) -> Result<JsonValue> {
|
pub async fn req(user: User, target: Ref) -> Result<JsonValue> {
|
||||||
let col = get_collection("users");
|
let col = get_collection("users");
|
||||||
|
|
||||||
|
let target = target.fetch_user().await?;
|
||||||
|
|
||||||
match get_relationship(&user, &target.id) {
|
match get_relationship(&user, &target.id) {
|
||||||
RelationshipStatus::Friend
|
RelationshipStatus::Friend
|
||||||
| RelationshipStatus::Outgoing
|
| RelationshipStatus::Outgoing
|
||||||
@@ -43,21 +45,27 @@ pub async fn req(user: User, target: Ref) -> Result<JsonValue> {
|
|||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
try_join!(
|
let target = target
|
||||||
|
.from_override(&user, RelationshipStatus::None)
|
||||||
|
.await?;
|
||||||
|
let user = user
|
||||||
|
.from_override(&target, RelationshipStatus::None)
|
||||||
|
.await?;
|
||||||
|
let target_id = target.id.clone();
|
||||||
|
|
||||||
ClientboundNotification::UserRelationship {
|
ClientboundNotification::UserRelationship {
|
||||||
id: user.id.clone(),
|
id: user.id.clone(),
|
||||||
user: target.id.clone(),
|
user: target,
|
||||||
status: RelationshipStatus::None
|
status: RelationshipStatus::None,
|
||||||
}
|
}
|
||||||
.publish(user.id.clone()),
|
.publish(user.id.clone());
|
||||||
|
|
||||||
ClientboundNotification::UserRelationship {
|
ClientboundNotification::UserRelationship {
|
||||||
id: target.id.clone(),
|
id: target_id.clone(),
|
||||||
user: user.id.clone(),
|
user,
|
||||||
status: RelationshipStatus::None
|
status: RelationshipStatus::None,
|
||||||
}
|
}
|
||||||
.publish(target.id.clone())
|
.publish(target_id);
|
||||||
)
|
|
||||||
.ok();
|
|
||||||
|
|
||||||
Ok(json!({ "status": "None" }))
|
Ok(json!({ "status": "None" }))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,10 +9,10 @@ use rocket_contrib::json::JsonValue;
|
|||||||
#[delete("/<target>/block")]
|
#[delete("/<target>/block")]
|
||||||
pub async fn req(user: User, target: Ref) -> Result<JsonValue> {
|
pub async fn req(user: User, target: Ref) -> Result<JsonValue> {
|
||||||
let col = get_collection("users");
|
let col = get_collection("users");
|
||||||
|
let target = target.fetch_user().await?;
|
||||||
|
|
||||||
match get_relationship(&user, &target.id) {
|
match get_relationship(&user, &target.id) {
|
||||||
RelationshipStatus::Blocked => {
|
RelationshipStatus::Blocked => match get_relationship(&target, &user.id) {
|
||||||
match get_relationship(&target.fetch_user().await?, &user.id) {
|
|
||||||
RelationshipStatus::Blocked => {
|
RelationshipStatus::Blocked => {
|
||||||
col.update_one(
|
col.update_one(
|
||||||
doc! {
|
doc! {
|
||||||
@@ -32,14 +32,15 @@ pub async fn req(user: User, target: Ref) -> Result<JsonValue> {
|
|||||||
with: "user",
|
with: "user",
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
let target = target
|
||||||
|
.from_override(&user, RelationshipStatus::BlockedOther)
|
||||||
|
.await?;
|
||||||
ClientboundNotification::UserRelationship {
|
ClientboundNotification::UserRelationship {
|
||||||
id: user.id.clone(),
|
id: user.id.clone(),
|
||||||
user: target.id.clone(),
|
user: target,
|
||||||
status: RelationshipStatus::BlockedOther,
|
status: RelationshipStatus::BlockedOther,
|
||||||
}
|
}
|
||||||
.publish(user.id.clone())
|
.publish(user.id.clone());
|
||||||
.await
|
|
||||||
.ok();
|
|
||||||
|
|
||||||
Ok(json!({ "status": "BlockedOther" }))
|
Ok(json!({ "status": "BlockedOther" }))
|
||||||
}
|
}
|
||||||
@@ -73,21 +74,27 @@ pub async fn req(user: User, target: Ref) -> Result<JsonValue> {
|
|||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
try_join!(
|
let target = target
|
||||||
|
.from_override(&user, RelationshipStatus::None)
|
||||||
|
.await?;
|
||||||
|
let user = user
|
||||||
|
.from_override(&target, RelationshipStatus::None)
|
||||||
|
.await?;
|
||||||
|
let target_id = target.id.clone();
|
||||||
|
|
||||||
ClientboundNotification::UserRelationship {
|
ClientboundNotification::UserRelationship {
|
||||||
id: user.id.clone(),
|
id: user.id.clone(),
|
||||||
user: target.id.clone(),
|
user: target,
|
||||||
status: RelationshipStatus::None
|
status: RelationshipStatus::None,
|
||||||
}
|
}
|
||||||
.publish(user.id.clone()),
|
.publish(user.id.clone());
|
||||||
|
|
||||||
ClientboundNotification::UserRelationship {
|
ClientboundNotification::UserRelationship {
|
||||||
id: target.id.clone(),
|
id: target_id.clone(),
|
||||||
user: user.id.clone(),
|
user: user,
|
||||||
status: RelationshipStatus::None
|
status: RelationshipStatus::None,
|
||||||
}
|
}
|
||||||
.publish(target.id.clone())
|
.publish(target_id);
|
||||||
)
|
|
||||||
.ok();
|
|
||||||
|
|
||||||
Ok(json!({ "status": "None" }))
|
Ok(json!({ "status": "None" }))
|
||||||
}
|
}
|
||||||
@@ -98,8 +105,7 @@ pub async fn req(user: User, target: Ref) -> Result<JsonValue> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => Err(Error::InternalError),
|
_ => Err(Error::InternalError),
|
||||||
}
|
},
|
||||||
}
|
|
||||||
_ => Err(Error::NoEffect),
|
_ => Err(Error::NoEffect),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -18,6 +18,8 @@ lazy_static! {
|
|||||||
|
|
||||||
pub static ref AUTUMN_URL: String =
|
pub static ref AUTUMN_URL: String =
|
||||||
env::var("AUTUMN_PUBLIC_URL").unwrap_or_else(|_| "https://example.com".to_string());
|
env::var("AUTUMN_PUBLIC_URL").unwrap_or_else(|_| "https://example.com".to_string());
|
||||||
|
pub static ref JANUARY_URL: String =
|
||||||
|
env::var("JANUARY_PUBLIC_URL").unwrap_or_else(|_| "https://example.com".to_string());
|
||||||
pub static ref VOSO_URL: String =
|
pub static ref VOSO_URL: String =
|
||||||
env::var("VOSO_PUBLIC_URL").unwrap_or_else(|_| "https://example.com".to_string());
|
env::var("VOSO_PUBLIC_URL").unwrap_or_else(|_| "https://example.com".to_string());
|
||||||
pub static ref VOSO_WS_HOST: String =
|
pub static ref VOSO_WS_HOST: String =
|
||||||
@@ -47,6 +49,7 @@ lazy_static! {
|
|||||||
pub static ref USE_HCAPTCHA: bool = env::var("REVOLT_HCAPTCHA_KEY").is_ok();
|
pub static ref USE_HCAPTCHA: bool = env::var("REVOLT_HCAPTCHA_KEY").is_ok();
|
||||||
pub static ref USE_PROMETHEUS: bool = env::var("REVOLT_ENABLE_PROMETHEUS").map_or(false, |v| v == "1");
|
pub static ref USE_PROMETHEUS: bool = env::var("REVOLT_ENABLE_PROMETHEUS").map_or(false, |v| v == "1");
|
||||||
pub static ref USE_AUTUMN: bool = env::var("AUTUMN_PUBLIC_URL").is_ok();
|
pub static ref USE_AUTUMN: bool = env::var("AUTUMN_PUBLIC_URL").is_ok();
|
||||||
|
pub static ref USE_JANUARY: bool = env::var("JANUARY_PUBLIC_URL").is_ok();
|
||||||
pub static ref USE_VOSO: bool = env::var("VOSO_PUBLIC_URL").is_ok() && env::var("VOSO_MANAGE_TOKEN").is_ok();
|
pub static ref USE_VOSO: bool = env::var("VOSO_PUBLIC_URL").is_ok() && env::var("VOSO_MANAGE_TOKEN").is_ok();
|
||||||
|
|
||||||
// SMTP Settings
|
// SMTP Settings
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
pub const VERSION: &str = "0.4.1-alpha.11";
|
||||||
Reference in new issue
Block a user