Compare commits
63
Commits
20220606-1
...
20220918-1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
86ad72b426 | ||
|
|
044d82d566 | ||
|
|
deffe663cb | ||
|
|
e7f6433d77 | ||
|
|
345e4d5f13 | ||
|
|
81fc3e7760 | ||
|
|
ca8d2e6118 | ||
|
|
c1227e8e33 | ||
|
|
0e0c6f2c3a | ||
|
|
fa82be74b5 | ||
|
|
8491ced13d | ||
|
|
229d4e2e1d | ||
|
|
7bdd2d69d6 | ||
|
|
ab2af9b5e5 | ||
|
|
5aebda2d16 | ||
|
|
a7273d0556 | ||
|
|
72aed64e5b | ||
|
|
00ad4d54e7 | ||
|
|
cba8c4ef3b | ||
|
|
b6e395fe0d | ||
|
|
8d07457c58 | ||
|
|
f1171e5358 | ||
|
|
d96c9f62c4 | ||
|
|
4f73e43a03 | ||
|
|
741b8ee8fd | ||
|
|
692081b7f0 | ||
|
|
943b1f08f0 | ||
|
|
0e5c8ed59b | ||
|
|
a7e0c42ee4 | ||
|
|
386f027a5a | ||
|
|
30c7f553d8 | ||
|
|
e291eef364 | ||
|
|
73443ce883 | ||
|
|
47297e243f | ||
|
|
4188b6d2f2 | ||
|
|
165380c7b4 | ||
|
|
49ca24ac9f | ||
|
|
35f956ce7d | ||
|
|
4baab5d5d5 | ||
|
|
7fc4fb2df7 | ||
|
|
ef757aa2fb | ||
|
|
0585dd0c20 | ||
|
|
f96541efab | ||
|
|
c6414338b6 | ||
|
|
4c4eb60cdb | ||
|
|
11d89b3bf0 | ||
|
|
64bb171cc8 | ||
|
|
6de5ad15c5 | ||
|
|
b5ab16d66f | ||
|
|
b9aad6d38c | ||
|
|
ed5c8159e9 | ||
|
|
2e6e1fdd44 | ||
|
|
cb2e6fb2f7 | ||
|
|
d069acd5a5 | ||
|
|
f8b5c25c17 | ||
|
|
80af18761f | ||
|
|
98e10c3478 | ||
|
|
2051c8ce45 | ||
|
|
0abc12e5fa | ||
|
|
aaceb5bdee | ||
|
|
3de7ee4c7b | ||
|
|
d660127c14 | ||
|
|
7390b3c087 |
No files matched your search
@@ -0,0 +1,79 @@
|
|||||||
|
# MongoDB URI
|
||||||
|
MONGODB=mongodb://localhost
|
||||||
|
|
||||||
|
# URL to where the Revolt app is publicly accessible
|
||||||
|
REVOLT_APP_URL=http://local.revolt.chat:5000
|
||||||
|
|
||||||
|
# URL to where the API is publicly accessible
|
||||||
|
REVOLT_PUBLIC_URL=http://local.revolt.chat:8000
|
||||||
|
VITE_API_URL=http://local.revolt.chat:8000
|
||||||
|
|
||||||
|
# URL to where the WebSocket server is publicly accessible
|
||||||
|
REVOLT_EXTERNAL_WS_URL=ws://local.revolt.chat:9000
|
||||||
|
|
||||||
|
# URL to where Autumn is publicly available
|
||||||
|
AUTUMN_PUBLIC_URL=http://local.revolt.chat:3000
|
||||||
|
|
||||||
|
# URL to where January is publicly available
|
||||||
|
JANUARY_PUBLIC_URL=http://local.revolt.chat:7000
|
||||||
|
|
||||||
|
# URL to where Vortex is publicly available
|
||||||
|
# VOSO_PUBLIC_URL=https://voso.revolt.chat
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
## hCaptcha Settings
|
||||||
|
##
|
||||||
|
|
||||||
|
# If you are sure that you don't want to use hCaptcha, set to 1.
|
||||||
|
REVOLT_UNSAFE_NO_CAPTCHA=1
|
||||||
|
|
||||||
|
# hCaptcha API key
|
||||||
|
# REVOLT_HCAPTCHA_KEY=0x0000000000000000000000000000000000000000
|
||||||
|
|
||||||
|
# hCaptcha site key
|
||||||
|
# REVOLT_HCAPTCHA_SITEKEY=10000000-ffff-ffff-ffff-000000000001
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
## Email Settings
|
||||||
|
##
|
||||||
|
|
||||||
|
# If you are sure that you don't want to use email verification, set to 1.
|
||||||
|
REVOLT_UNSAFE_NO_EMAIL=1
|
||||||
|
|
||||||
|
# SMTP host
|
||||||
|
# REVOLT_SMTP_HOST=smtp.example.com
|
||||||
|
|
||||||
|
# SMTP username
|
||||||
|
# REVOLT_SMTP_USERNAME=noreply@example.com
|
||||||
|
|
||||||
|
# SMTP password
|
||||||
|
# REVOLT_SMTP_PASSWORD=CHANGEME
|
||||||
|
|
||||||
|
# SMTP From header
|
||||||
|
# REVOLT_SMTP_FROM=Revolt <noreply@example.com>
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
## Application Settings
|
||||||
|
##
|
||||||
|
|
||||||
|
# Whether to only allow users to sign up if they have an invite code
|
||||||
|
REVOLT_INVITE_ONLY=0
|
||||||
|
|
||||||
|
# Maximum number of people that can be in a group chat
|
||||||
|
REVOLT_MAX_GROUP_SIZE=150
|
||||||
|
|
||||||
|
# VAPID keys for push notifications
|
||||||
|
# Generate using this guide: https://gitlab.insrt.uk/revolt/delta/-/wikis/vapid
|
||||||
|
# --> Please replace these keys before going into production! <--
|
||||||
|
REVOLT_VAPID_PRIVATE_KEY=LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IY0NBUUVFSUJSUWpyTWxLRnBiVWhsUHpUbERvcEliYk1yeVNrNXpKYzVYVzIxSjJDS3hvQW9HQ0NxR1NNNDkKQXdFSG9VUURRZ0FFWnkrQkg2TGJQZ2hEa3pEempXOG0rUXVPM3pCajRXT1phdkR6ZU00c0pqbmFwd1psTFE0WAp1ZDh2TzVodU94QWhMQlU3WWRldVovWHlBdFpWZmNyQi9BPT0KLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQo=
|
||||||
|
REVOLT_VAPID_PUBLIC_KEY=BGcvgR-i2z4IQ5Mw841vJvkLjt8wY-FjmWrw83jOLCY52qcGZS0OF7nfLzuYbjsQISwVO2HXrmf18gLWVX3Kwfw=
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
## Vortex configuration
|
||||||
|
##
|
||||||
|
|
||||||
|
# VOSO_MANAGE_TOKEN=CHANGEME
|
||||||
@@ -1,2 +1,5 @@
|
|||||||
Rocket.toml
|
Rocket.toml
|
||||||
target
|
target
|
||||||
|
.data
|
||||||
|
|
||||||
|
.vercel
|
||||||
Generated
+259
-799
File diff suppressed because it is too large.
Load diff
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "revolt-bonfire"
|
name = "revolt-bonfire"
|
||||||
version = "1.0.6-patch.2"
|
version = "0.5.5"
|
||||||
license = "AGPL-3.0-or-later"
|
license = "AGPL-3.0-or-later"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ mod websocket;
|
|||||||
#[async_std::main]
|
#[async_std::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
// Configure requirements for Bonfire.
|
// Configure requirements for Bonfire.
|
||||||
let _guard = revolt_quark::setup_logging();
|
revolt_quark::configure!();
|
||||||
database::connect().await;
|
database::connect().await;
|
||||||
|
|
||||||
// Clean up the current region information.
|
// Clean up the current region information.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "revolt-delta"
|
name = "revolt-delta"
|
||||||
version = "0.5.3-6"
|
version = "0.5.5"
|
||||||
license = "AGPL-3.0-or-later"
|
license = "AGPL-3.0-or-later"
|
||||||
authors = ["Paul Makles <paulmakles@gmail.com>"]
|
authors = ["Paul Makles <paulmakles@gmail.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
@@ -18,7 +18,6 @@ linkify = "0.6.0"
|
|||||||
once_cell = "1.4.1"
|
once_cell = "1.4.1"
|
||||||
env_logger = "0.7.1"
|
env_logger = "0.7.1"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
ctrlc = { version = "3.0", features = ["termination"] }
|
|
||||||
|
|
||||||
# Lang. Utilities
|
# Lang. Utilities
|
||||||
regex = "1"
|
regex = "1"
|
||||||
@@ -44,7 +43,6 @@ async-std = { version = "1.8.0", features = ["tokio1", "tokio02", "attributes"]
|
|||||||
|
|
||||||
# internal util
|
# internal util
|
||||||
lettre = "0.10.0-alpha.4"
|
lettre = "0.10.0-alpha.4"
|
||||||
rauth = { git = "https://github.com/insertish/rauth", rev = "001a9698c56cea79e69e4ae71d7bc2cb48aec1a6" }
|
|
||||||
|
|
||||||
# redis
|
# redis
|
||||||
redis = { version = "0.21.2", features = ["async-std-comp"] }
|
redis = { version = "0.21.2", features = ["async-std-comp"] }
|
||||||
@@ -52,15 +50,14 @@ mobc = { version = "0.7.3" }
|
|||||||
mobc-redis = { version = "0.7.0", default-features = false, features = ["async-std-comp"] }
|
mobc-redis = { version = "0.7.0", default-features = false, features = ["async-std-comp"] }
|
||||||
|
|
||||||
# web
|
# web
|
||||||
rocket_empty = { git = "https://github.com/insertish/rocket_empty", branch = "rc1" }
|
rocket = { version = "0.5.0-rc.2", default-features = false, features = ["json"] }
|
||||||
rocket = { version = "0.5.0-rc.1", default-features = false, features = ["json"] }
|
rocket_empty = { git = "https://github.com/insertish/rocket_empty", branch = "master" }
|
||||||
mongodb = { version = "1.2.2", features = ["async-std-runtime"], default-features = false }
|
rocket_rauth = { git = "https://github.com/insertish/rauth", rev = "9c3f9bb58a549a51546bd748274130ac4a667cb4" }
|
||||||
rocket_cors = { git = "https://github.com/lawliet89/rocket_cors", rev = "5843861a88958c16bfaa0b40f0d8910772bcd2f6" }
|
|
||||||
|
|
||||||
# spec generation
|
# spec generation
|
||||||
schemars = "0.8.8"
|
schemars = "0.8.8"
|
||||||
# rocket_okapi = "0.8.0-rc.1"
|
# rocket_okapi = "0.8.0-rc.1"
|
||||||
rocket_okapi = { git = "https://github.com/insertish/okapi", rev = "dcf0df115596ee07a587a7a543cddf3d7944645b", features = [ "swagger" ] }
|
rocket_okapi = { git = "https://github.com/insertish/okapi", rev = "a1048d0c8cd771e424ec97d33d825c32e06aa120", features = [ "swagger" ] }
|
||||||
|
|
||||||
# quark
|
# quark
|
||||||
revolt-quark = { path = "../quark" }
|
revolt-quark = { path = "../quark" }
|
||||||
+21
-119
@@ -6,144 +6,46 @@ extern crate rocket_okapi;
|
|||||||
extern crate serde_json;
|
extern crate serde_json;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate lazy_static;
|
extern crate lazy_static;
|
||||||
extern crate ctrlc;
|
|
||||||
|
|
||||||
pub mod routes;
|
pub mod routes;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
pub mod version;
|
|
||||||
|
|
||||||
use log::info;
|
use revolt_quark::rauth::RAuth;
|
||||||
use rauth::{
|
|
||||||
config::{Captcha, Config, EmailVerification, SMTPSettings, Template, Templates},
|
|
||||||
logic::Auth,
|
|
||||||
};
|
|
||||||
use revolt_quark::variables::delta::{
|
|
||||||
APP_URL, HCAPTCHA_KEY, INVITE_ONLY, SMTP_FROM, SMTP_HOST, SMTP_PASSWORD, SMTP_USERNAME,
|
|
||||||
USE_EMAIL, USE_HCAPTCHA,
|
|
||||||
};
|
|
||||||
use revolt_quark::DatabaseInfo;
|
use revolt_quark::DatabaseInfo;
|
||||||
use rocket_cors::AllowedOrigins;
|
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
#[async_std::main]
|
#[launch]
|
||||||
async fn main() {
|
async fn rocket() -> _ {
|
||||||
let _guard = revolt_quark::setup_logging();
|
// Configure logging and environment
|
||||||
|
revolt_quark::configure!();
|
||||||
info!(
|
|
||||||
"Starting Revolt server [version {}].",
|
|
||||||
crate::version::VERSION
|
|
||||||
);
|
|
||||||
|
|
||||||
|
// Ensure environment variables are present
|
||||||
revolt_quark::variables::delta::preflight_checks();
|
revolt_quark::variables::delta::preflight_checks();
|
||||||
|
|
||||||
#[cfg(debug_assertions)]
|
// Setup database
|
||||||
ctrlc::set_handler(move || {
|
|
||||||
// Force ungraceful exit to avoid hang.
|
|
||||||
std::process::exit(0);
|
|
||||||
})
|
|
||||||
.expect("Error setting Ctrl-C handler");
|
|
||||||
|
|
||||||
let cors = rocket_cors::CorsOptions {
|
|
||||||
allowed_origins: AllowedOrigins::All,
|
|
||||||
allowed_methods: [
|
|
||||||
"Get", "Put", "Post", "Delete", "Options", "Head", "Trace", "Connect", "Patch",
|
|
||||||
]
|
|
||||||
.iter()
|
|
||||||
.map(|s| FromStr::from_str(s).unwrap())
|
|
||||||
.collect(),
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
.to_cors()
|
|
||||||
.expect("Failed to create CORS.");
|
|
||||||
|
|
||||||
let mut config = Config {
|
|
||||||
email_verification: if *USE_EMAIL {
|
|
||||||
EmailVerification::Enabled {
|
|
||||||
smtp: SMTPSettings {
|
|
||||||
from: (*SMTP_FROM).to_string(),
|
|
||||||
host: (*SMTP_HOST).to_string(),
|
|
||||||
username: (*SMTP_USERNAME).to_string(),
|
|
||||||
password: (*SMTP_PASSWORD).to_string(),
|
|
||||||
reply_to: Some("support@revolt.chat".into()),
|
|
||||||
port: None,
|
|
||||||
use_tls: None,
|
|
||||||
},
|
|
||||||
expiry: Default::default(),
|
|
||||||
templates: Templates {
|
|
||||||
verify: Template {
|
|
||||||
title: "Verify your Revolt account.".into(),
|
|
||||||
text: include_str!(crate::asset!("templates/verify.txt")).into(),
|
|
||||||
url: format!("{}/login/verify/", *APP_URL),
|
|
||||||
html: None,
|
|
||||||
},
|
|
||||||
reset: Template {
|
|
||||||
title: "Reset your Revolt password.".into(),
|
|
||||||
text: include_str!(crate::asset!("templates/reset.txt")).into(),
|
|
||||||
url: format!("{}/login/reset/", *APP_URL),
|
|
||||||
html: None,
|
|
||||||
},
|
|
||||||
welcome: None,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
EmailVerification::Disabled
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
if *INVITE_ONLY {
|
|
||||||
config.invite_only = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if *USE_HCAPTCHA {
|
|
||||||
config.captcha = Captcha::HCaptcha {
|
|
||||||
secret: HCAPTCHA_KEY.clone(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
let db = DatabaseInfo::Auto.connect().await.unwrap();
|
let db = DatabaseInfo::Auto.connect().await.unwrap();
|
||||||
db.migrate_database().await.unwrap();
|
db.migrate_database().await.unwrap();
|
||||||
|
|
||||||
// This is entirely temporary code until rauth is migrated to quark.
|
// Setup rAuth
|
||||||
// (and / or otherwise gets updated to MongoDB v2 driver)
|
let rauth = RAuth {
|
||||||
let mongo_db = mongodb::Client::with_uri_str(
|
database: db.clone().into(),
|
||||||
&std::env::var("MONGODB").unwrap_or_else(|_| "mongodb://localhost".to_string()),
|
config: revolt_quark::util::rauth::config(),
|
||||||
)
|
};
|
||||||
.await
|
|
||||||
.expect("Failed to init db connection.");
|
|
||||||
|
|
||||||
rauth::entities::sync_models(&mongo_db.database("revolt")).await;
|
|
||||||
|
|
||||||
// Launch background task workers.
|
// Launch background task workers.
|
||||||
async_std::task::spawn(revolt_quark::tasks::start_workers(db.clone()));
|
async_std::task::spawn(revolt_quark::tasks::start_workers(db.clone()));
|
||||||
|
|
||||||
let auth = Auth::new(mongo_db.database("revolt"), config);
|
// Configure CORS
|
||||||
|
let cors = revolt_quark::web::cors::new();
|
||||||
|
|
||||||
|
// Configure Rocket
|
||||||
let rocket = rocket::build();
|
let rocket = rocket::build();
|
||||||
routes::mount(rocket)
|
routes::mount(rocket)
|
||||||
.mount("/", rocket_cors::catch_all_options_routes())
|
.mount("/", revolt_quark::web::cors::catch_all_options_routes())
|
||||||
.mount("/", util::ratelimiter::routes())
|
.mount("/", revolt_quark::web::ratelimiter::routes())
|
||||||
.mount(
|
.mount("/swagger/", revolt_quark::web::swagger::routes())
|
||||||
"/swagger/",
|
.manage(rauth)
|
||||||
rocket_okapi::swagger_ui::make_swagger_ui(&rocket_okapi::swagger_ui::SwaggerUIConfig {
|
|
||||||
url: "../openapi.json".to_owned(),
|
|
||||||
..Default::default()
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.manage(auth)
|
|
||||||
.manage(db)
|
.manage(db)
|
||||||
.manage(cors.clone())
|
.manage(cors.clone())
|
||||||
.attach(util::ratelimiter::RatelimitFairing)
|
.attach(revolt_quark::web::ratelimiter::RatelimitFairing)
|
||||||
.attach(cors)
|
.attach(cors)
|
||||||
.launch()
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resolve asset
|
|
||||||
macro_rules! asset {
|
|
||||||
($path:literal) => {
|
|
||||||
concat!(env!("CARGO_MANIFEST_DIR"), "/assets/", $path)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) use asset;
|
|
||||||
@@ -2,13 +2,27 @@ use revolt_quark::{
|
|||||||
models::{channel::PartialChannel, Channel, User},
|
models::{channel::PartialChannel, Channel, User},
|
||||||
perms, Db, EmptyResponse, Error, Permission, Ref, Result,
|
perms, Db, EmptyResponse, Error, Permission, Ref, Result,
|
||||||
};
|
};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use validator::Validate;
|
||||||
|
|
||||||
|
/// # Query Parameters
|
||||||
|
#[derive(Validate, Serialize, Deserialize, JsonSchema, FromForm)]
|
||||||
|
pub struct OptionsChannelDelete {
|
||||||
|
/// Whether to not send a leave message
|
||||||
|
leave_silently: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
/// # Close Channel
|
/// # Close Channel
|
||||||
///
|
///
|
||||||
/// Deletes a server channel, leaves a group or closes a group.
|
/// Deletes a server channel, leaves a group or closes a group.
|
||||||
#[openapi(tag = "Channel Information")]
|
#[openapi(tag = "Channel Information")]
|
||||||
#[delete("/<target>")]
|
#[delete("/<target>?<options..>")]
|
||||||
pub async fn req(db: &Db, user: User, target: Ref) -> Result<EmptyResponse> {
|
pub async fn req(
|
||||||
|
db: &Db,
|
||||||
|
user: User,
|
||||||
|
target: Ref,
|
||||||
|
options: OptionsChannelDelete,
|
||||||
|
) -> Result<EmptyResponse> {
|
||||||
let mut channel = target.as_channel(db).await?;
|
let mut channel = target.as_channel(db).await?;
|
||||||
let mut perms = perms(&user).channel(&channel);
|
let mut perms = perms(&user).channel(&channel);
|
||||||
perms.throw_permission(db, Permission::ViewChannel).await?;
|
perms.throw_permission(db, Permission::ViewChannel).await?;
|
||||||
@@ -27,7 +41,12 @@ pub async fn req(db: &Db, user: User, target: Ref) -> Result<EmptyResponse> {
|
|||||||
.await
|
.await
|
||||||
.map(|_| EmptyResponse),
|
.map(|_| EmptyResponse),
|
||||||
Channel::Group { .. } => channel
|
Channel::Group { .. } => channel
|
||||||
.remove_user_from_group(db, &user.id, None)
|
.remove_user_from_group(
|
||||||
|
db,
|
||||||
|
&user.id,
|
||||||
|
None,
|
||||||
|
options.leave_silently.unwrap_or_default(),
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.map(|_| EmptyResponse),
|
.map(|_| EmptyResponse),
|
||||||
Channel::TextChannel { .. } | Channel::VoiceChannel { .. } => {
|
Channel::TextChannel { .. } | Channel::VoiceChannel { .. } => {
|
||||||
|
|||||||
@@ -16,20 +16,21 @@ use validator::Validate;
|
|||||||
pub struct DataEditChannel {
|
pub struct DataEditChannel {
|
||||||
/// Channel name
|
/// Channel name
|
||||||
#[validate(length(min = 1, max = 32))]
|
#[validate(length(min = 1, max = 32))]
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
name: Option<String>,
|
name: Option<String>,
|
||||||
/// Channel description
|
/// Channel description
|
||||||
#[validate(length(min = 0, max = 1024))]
|
#[validate(length(min = 0, max = 1024))]
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
description: Option<String>,
|
description: Option<String>,
|
||||||
|
/// Group owner
|
||||||
|
owner: Option<String>,
|
||||||
/// Icon
|
/// Icon
|
||||||
///
|
///
|
||||||
/// Provide an Autumn attachment Id.
|
/// Provide an Autumn attachment Id.
|
||||||
#[validate(length(min = 1, max = 128))]
|
#[validate(length(min = 1, max = 128))]
|
||||||
icon: Option<String>,
|
icon: Option<String>,
|
||||||
/// Whether this channel is age-restricted
|
/// Whether this channel is age-restricted
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
nsfw: Option<bool>,
|
nsfw: Option<bool>,
|
||||||
|
/// Whether this channel is archived
|
||||||
|
archived: Option<bool>,
|
||||||
#[validate(length(min = 1))]
|
#[validate(length(min = 1))]
|
||||||
remove: Option<Vec<FieldsChannel>>,
|
remove: Option<Vec<FieldsChannel>>,
|
||||||
}
|
}
|
||||||
@@ -59,12 +60,48 @@ pub async fn req(
|
|||||||
&& data.description.is_none()
|
&& data.description.is_none()
|
||||||
&& data.icon.is_none()
|
&& data.icon.is_none()
|
||||||
&& data.nsfw.is_none()
|
&& data.nsfw.is_none()
|
||||||
|
&& data.owner.is_none()
|
||||||
&& data.remove.is_none()
|
&& data.remove.is_none()
|
||||||
{
|
{
|
||||||
return Ok(Json(channel));
|
return Ok(Json(channel));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut partial: PartialChannel = Default::default();
|
let mut partial: PartialChannel = Default::default();
|
||||||
|
|
||||||
|
// Transfer group ownership
|
||||||
|
if let Some(new_owner) = data.owner {
|
||||||
|
if let Channel::Group {
|
||||||
|
owner, recipients, ..
|
||||||
|
} = &mut channel
|
||||||
|
{
|
||||||
|
// Make sure we are the owner of this group
|
||||||
|
if owner != &user.id {
|
||||||
|
return Err(Error::NotOwner);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure user is part of group
|
||||||
|
if !recipients.contains(&new_owner) {
|
||||||
|
return Err(Error::NotInGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transfer ownership
|
||||||
|
partial.owner = Some(new_owner.to_string());
|
||||||
|
let old_owner = std::mem::replace(owner, new_owner.to_string());
|
||||||
|
|
||||||
|
// Notify clients
|
||||||
|
SystemMessage::ChannelOwnershipChanged {
|
||||||
|
from: old_owner,
|
||||||
|
to: new_owner,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(Error::InvalidOperation);
|
||||||
|
}
|
||||||
|
.into_message(channel.id().to_string())
|
||||||
|
.create(db, &channel, None)
|
||||||
|
.await
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
|
||||||
match &mut channel {
|
match &mut channel {
|
||||||
Channel::Group {
|
Channel::Group {
|
||||||
id,
|
id,
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ pub async fn req(db: &Db, user: User, target: Ref, member: Ref) -> Result<EmptyR
|
|||||||
}
|
}
|
||||||
|
|
||||||
channel
|
channel
|
||||||
.remove_user_from_group(db, &member.id, Some(&user.id))
|
.remove_user_from_group(db, &member.id, Some(&user.id), false)
|
||||||
.await
|
.await
|
||||||
.map(|_| EmptyResponse)
|
.map(|_| EmptyResponse)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
use revolt_quark::{
|
||||||
|
models::{message::PartialMessage, User},
|
||||||
|
perms, Db, EmptyResponse, Permission, Ref, Result,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// # Remove All Reactions from Message
|
||||||
|
///
|
||||||
|
/// Remove your own, someone else's or all of a given reaction.
|
||||||
|
///
|
||||||
|
/// Requires `ManageMessages` permission.
|
||||||
|
#[openapi(tag = "Interactions")]
|
||||||
|
#[delete("/<target>/messages/<msg>/reactions")]
|
||||||
|
pub async fn clear_reactions(db: &Db, user: User, target: Ref, msg: Ref) -> Result<EmptyResponse> {
|
||||||
|
let channel = target.as_channel(db).await?;
|
||||||
|
perms(&user)
|
||||||
|
.channel(&channel)
|
||||||
|
.throw_permission_and_view_channel(db, Permission::ManageMessages)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Fetch relevant message
|
||||||
|
let mut message = msg.as_message_in(db, channel.id()).await?;
|
||||||
|
|
||||||
|
// Clear reactions
|
||||||
|
message
|
||||||
|
.update(
|
||||||
|
db,
|
||||||
|
PartialMessage {
|
||||||
|
reactions: Some(Default::default()),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map(|_| EmptyResponse)
|
||||||
|
}
|
||||||
@@ -36,6 +36,8 @@ pub async fn req(
|
|||||||
edit.validate()
|
edit.validate()
|
||||||
.map_err(|error| Error::FailedValidation { error })?;
|
.map_err(|error| Error::FailedValidation { error })?;
|
||||||
|
|
||||||
|
Message::validate_sum(&edit.content, &edit.embeds)?;
|
||||||
|
|
||||||
let mut message = msg.as_message(db).await?;
|
let mut message = msg.as_message(db).await?;
|
||||||
if message.channel != target {
|
if message.channel != target {
|
||||||
return Err(Error::NotFound);
|
return Err(Error::NotFound);
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
use revolt_quark::{models::User, perms, Db, EmptyResponse, Permission, Ref, Result};
|
||||||
|
|
||||||
|
/// # Add Reaction to Message
|
||||||
|
///
|
||||||
|
/// React to a given message.
|
||||||
|
#[openapi(tag = "Interactions")]
|
||||||
|
#[put("/<target>/messages/<msg>/reactions/<emoji>")]
|
||||||
|
pub async fn react_message(
|
||||||
|
db: &Db,
|
||||||
|
user: User,
|
||||||
|
target: Ref,
|
||||||
|
msg: Ref,
|
||||||
|
emoji: Ref,
|
||||||
|
) -> Result<EmptyResponse> {
|
||||||
|
let channel = target.as_channel(db).await?;
|
||||||
|
perms(&user)
|
||||||
|
.channel(&channel)
|
||||||
|
.throw_permission_and_view_channel(db, Permission::React)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Fetch relevant message
|
||||||
|
let message = msg.as_message_in(db, channel.id()).await?;
|
||||||
|
|
||||||
|
// Add the reaction
|
||||||
|
message
|
||||||
|
.add_reaction(db, &user, &emoji.id)
|
||||||
|
.await
|
||||||
|
.map(|_| EmptyResponse)
|
||||||
|
}
|
||||||
@@ -2,10 +2,12 @@ use std::collections::HashSet;
|
|||||||
|
|
||||||
use revolt_quark::{
|
use revolt_quark::{
|
||||||
models::{
|
models::{
|
||||||
message::{Masquerade, Reply, SendableEmbed},
|
message::{Interactions, Masquerade, Reply, SendableEmbed},
|
||||||
Message, User,
|
Message, User,
|
||||||
},
|
},
|
||||||
perms, Db, Error, Permission, Ref, Result,
|
perms,
|
||||||
|
web::idempotency::IdempotencyKey,
|
||||||
|
Db, Error, Permission, Ref, Result,
|
||||||
};
|
};
|
||||||
|
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
@@ -14,13 +16,12 @@ use serde::{Deserialize, Serialize};
|
|||||||
use ulid::Ulid;
|
use ulid::Ulid;
|
||||||
use validator::Validate;
|
use validator::Validate;
|
||||||
|
|
||||||
use crate::util::idempotency::IdempotencyKey;
|
|
||||||
|
|
||||||
#[derive(Validate, Serialize, Deserialize, JsonSchema)]
|
#[derive(Validate, Serialize, Deserialize, JsonSchema)]
|
||||||
pub struct DataMessageSend {
|
pub struct DataMessageSend {
|
||||||
/// Unique token to prevent duplicate message sending
|
/// Unique token to prevent duplicate message sending
|
||||||
///
|
///
|
||||||
/// **This is deprecated and replaced by `Idempotency-Key`!**
|
/// **This is deprecated and replaced by `Idempotency-Key`!**
|
||||||
|
#[validate(length(min = 1, max = 64))]
|
||||||
nonce: Option<String>,
|
nonce: Option<String>,
|
||||||
|
|
||||||
/// Message content to send
|
/// Message content to send
|
||||||
@@ -32,11 +33,15 @@ pub struct DataMessageSend {
|
|||||||
/// Messages to reply to
|
/// Messages to reply to
|
||||||
replies: Option<Vec<Reply>>,
|
replies: Option<Vec<Reply>>,
|
||||||
/// Embeds to include in message
|
/// Embeds to include in message
|
||||||
|
///
|
||||||
|
/// Text embed content contributes to the content length cap
|
||||||
#[validate(length(min = 1, max = 10))]
|
#[validate(length(min = 1, max = 10))]
|
||||||
embeds: Option<Vec<SendableEmbed>>,
|
embeds: Option<Vec<SendableEmbed>>,
|
||||||
/// Masquerade to apply to this message
|
/// Masquerade to apply to this message
|
||||||
#[validate]
|
#[validate]
|
||||||
masquerade: Option<Masquerade>,
|
masquerade: Option<Masquerade>,
|
||||||
|
/// Information about how this message should be interacted with
|
||||||
|
interactions: Option<Interactions>,
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
@@ -60,6 +65,8 @@ pub async fn message_send(
|
|||||||
data.validate()
|
data.validate()
|
||||||
.map_err(|error| Error::FailedValidation { error })?;
|
.map_err(|error| Error::FailedValidation { error })?;
|
||||||
|
|
||||||
|
Message::validate_sum(&data.content, &data.embeds)?;
|
||||||
|
|
||||||
idempotency.consume_nonce(data.nonce).await?;
|
idempotency.consume_nonce(data.nonce).await?;
|
||||||
|
|
||||||
let channel = target.as_channel(db).await?;
|
let channel = target.as_channel(db).await?;
|
||||||
@@ -81,6 +88,7 @@ pub async fn message_send(
|
|||||||
channel: channel.id().to_string(),
|
channel: channel.id().to_string(),
|
||||||
author: user.id.clone(),
|
author: user.id.clone(),
|
||||||
masquerade: data.masquerade,
|
masquerade: data.masquerade,
|
||||||
|
interactions: data.interactions.unwrap_or_default(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -95,13 +103,22 @@ pub async fn message_send(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 2. Verify permissions for masquerade.
|
// 2. Verify permissions for masquerade.
|
||||||
if message.masquerade.is_some() {
|
if let Some(masq) = &message.masquerade {
|
||||||
permissions
|
permissions
|
||||||
.throw_permission(db, Permission::Masquerade)
|
.throw_permission(db, Permission::Masquerade)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
if masq.colour.is_some() {
|
||||||
|
permissions
|
||||||
|
.throw_permission(db, Permission::ManageRole)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Verify replies are valid.
|
// 3. Ensure interactions information is correct
|
||||||
|
message.interactions.validate(db, &mut permissions).await?;
|
||||||
|
|
||||||
|
// 4. Verify replies are valid.
|
||||||
let mut replies = HashSet::new();
|
let mut replies = HashSet::new();
|
||||||
if let Some(entries) = data.replies {
|
if let Some(entries) = data.replies {
|
||||||
if entries.len() > 5 {
|
if entries.len() > 5 {
|
||||||
@@ -134,7 +151,19 @@ pub async fn message_send(
|
|||||||
.replace(replies.into_iter().collect::<Vec<String>>());
|
.replace(replies.into_iter().collect::<Vec<String>>());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Add attachments to message.
|
// 5. Process included embeds.
|
||||||
|
let mut embeds = vec![];
|
||||||
|
if let Some(sendable_embeds) = data.embeds {
|
||||||
|
for sendable_embed in sendable_embeds {
|
||||||
|
embeds.push(sendable_embed.into_embed(db, message_id.clone()).await?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !embeds.is_empty() {
|
||||||
|
message.embeds.replace(embeds);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. Add attachments to message.
|
||||||
let mut attachments = vec![];
|
let mut attachments = vec![];
|
||||||
if let Some(ids) = &data.attachments {
|
if let Some(ids) = &data.attachments {
|
||||||
if !ids.is_empty() {
|
if !ids.is_empty() {
|
||||||
@@ -160,22 +189,10 @@ pub async fn message_send(
|
|||||||
message.attachments.replace(attachments);
|
message.attachments.replace(attachments);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. Process included embeds.
|
// 7. Set content
|
||||||
let mut embeds = vec![];
|
|
||||||
if let Some(sendable_embeds) = data.embeds {
|
|
||||||
for sendable_embed in sendable_embeds {
|
|
||||||
embeds.push(sendable_embed.into_embed(db, message_id.clone()).await?)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !embeds.is_empty() {
|
|
||||||
message.embeds.replace(embeds);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 6. Set content
|
|
||||||
message.content = data.content;
|
message.content = data.content;
|
||||||
|
|
||||||
// 7. Pass-through nonce value for clients
|
// 8. Pass-through nonce value for clients
|
||||||
message.nonce = Some(idempotency.into_key());
|
message.nonce = Some(idempotency.into_key());
|
||||||
|
|
||||||
message.create(db, &channel, Some(&user)).await?;
|
message.create(db, &channel, Some(&user)).await?;
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
use revolt_quark::{models::User, perms, Db, EmptyResponse, Permission, Ref, Result};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
/// # Query Parameters
|
||||||
|
#[derive(Serialize, Deserialize, JsonSchema, FromForm)]
|
||||||
|
pub struct OptionsUnreact {
|
||||||
|
/// Remove a specific user's reaction
|
||||||
|
user_id: Option<String>,
|
||||||
|
/// Remove all reactions
|
||||||
|
remove_all: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Remove Reaction(s) to Message
|
||||||
|
///
|
||||||
|
/// Remove your own, someone else's or all of a given reaction.
|
||||||
|
///
|
||||||
|
/// Requires `ManageMessages` if changing others' reactions.
|
||||||
|
#[openapi(tag = "Interactions")]
|
||||||
|
#[delete("/<target>/messages/<msg>/reactions/<emoji>?<options..>")]
|
||||||
|
pub async fn unreact_message(
|
||||||
|
db: &Db,
|
||||||
|
user: User,
|
||||||
|
target: Ref,
|
||||||
|
msg: Ref,
|
||||||
|
emoji: Ref,
|
||||||
|
options: OptionsUnreact,
|
||||||
|
) -> Result<EmptyResponse> {
|
||||||
|
let channel = target.as_channel(db).await?;
|
||||||
|
let mut permissions = perms(&user).channel(&channel);
|
||||||
|
permissions
|
||||||
|
.throw_permission_and_view_channel(db, Permission::React)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Check if we need to escalate permissions
|
||||||
|
let remove_all = options.remove_all.unwrap_or_default();
|
||||||
|
if options.user_id.is_some() || remove_all {
|
||||||
|
permissions
|
||||||
|
.throw_permission(db, Permission::ManageMessages)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch relevant message
|
||||||
|
let message = msg.as_message_in(db, channel.id()).await?;
|
||||||
|
|
||||||
|
// Check if we should wipe all of this reaction
|
||||||
|
if remove_all {
|
||||||
|
return message
|
||||||
|
.clear_reaction(db, &emoji.id)
|
||||||
|
.await
|
||||||
|
.map(|_| EmptyResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the reaction
|
||||||
|
message
|
||||||
|
.remove_reaction(db, options.user_id.as_ref().unwrap_or(&user.id), &emoji.id)
|
||||||
|
.await
|
||||||
|
.map(|_| EmptyResponse)
|
||||||
|
}
|
||||||
@@ -11,13 +11,16 @@ mod group_remove_member;
|
|||||||
mod invite_create;
|
mod invite_create;
|
||||||
mod members_fetch;
|
mod members_fetch;
|
||||||
mod message_bulk_delete;
|
mod message_bulk_delete;
|
||||||
|
mod message_clear_reactions;
|
||||||
mod message_delete;
|
mod message_delete;
|
||||||
mod message_edit;
|
mod message_edit;
|
||||||
mod message_fetch;
|
mod message_fetch;
|
||||||
mod message_query;
|
mod message_query;
|
||||||
mod message_query_stale;
|
mod message_query_stale;
|
||||||
|
mod message_react;
|
||||||
mod message_search;
|
mod message_search;
|
||||||
mod message_send;
|
mod message_send;
|
||||||
|
mod message_unreact;
|
||||||
mod permissions_set;
|
mod permissions_set;
|
||||||
mod permissions_set_default;
|
mod permissions_set_default;
|
||||||
mod voice_join;
|
mod voice_join;
|
||||||
@@ -44,5 +47,8 @@ pub fn routes() -> (Vec<Route>, OpenApi) {
|
|||||||
voice_join::req,
|
voice_join::req,
|
||||||
permissions_set::req,
|
permissions_set::req,
|
||||||
permissions_set_default::req,
|
permissions_set_default::req,
|
||||||
|
message_react::react_message,
|
||||||
|
message_unreact::unreact_message,
|
||||||
|
message_clear_reactions::clear_reactions
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
use revolt_quark::models::emoji::EmojiParent;
|
||||||
|
use revolt_quark::models::{Emoji, File, User};
|
||||||
|
use revolt_quark::{perms, Db, Error, Permission, Result};
|
||||||
|
use serde::Deserialize;
|
||||||
|
use validator::Validate;
|
||||||
|
|
||||||
|
use crate::util::regex::RE_EMOJI;
|
||||||
|
|
||||||
|
use rocket::serde::json::Json;
|
||||||
|
|
||||||
|
/// # Emoji Data
|
||||||
|
#[derive(Validate, Deserialize, JsonSchema)]
|
||||||
|
pub struct DataCreateEmoji {
|
||||||
|
/// Server name
|
||||||
|
#[validate(length(min = 1, max = 32), regex = "RE_EMOJI")]
|
||||||
|
name: String,
|
||||||
|
/// Parent information
|
||||||
|
parent: EmojiParent,
|
||||||
|
/// Whether the emoji is mature
|
||||||
|
#[serde(default)]
|
||||||
|
nsfw: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Create New Emoji
|
||||||
|
///
|
||||||
|
/// Create an emoji by its Autumn upload id.
|
||||||
|
#[openapi(tag = "Emojis")]
|
||||||
|
#[put("/emoji/<id>", data = "<data>")]
|
||||||
|
pub async fn create_emoji(
|
||||||
|
db: &Db,
|
||||||
|
user: User,
|
||||||
|
id: String,
|
||||||
|
data: Json<DataCreateEmoji>,
|
||||||
|
) -> Result<Json<Emoji>> {
|
||||||
|
let data = data.into_inner();
|
||||||
|
data.validate()
|
||||||
|
.map_err(|error| Error::FailedValidation { error })?;
|
||||||
|
|
||||||
|
// Bots cannot manage emojis
|
||||||
|
if user.bot.is_some() {
|
||||||
|
return Err(Error::IsBot);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate we have permission to write into parent
|
||||||
|
match &data.parent {
|
||||||
|
EmojiParent::Server { id } => {
|
||||||
|
let server = db.fetch_server(id).await?;
|
||||||
|
|
||||||
|
// Check for permission
|
||||||
|
perms(&user)
|
||||||
|
.server(&server)
|
||||||
|
.throw_permission(db, Permission::ManageCustomisation)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Check that there are no more than 100 emoji
|
||||||
|
// ! FIXME: hardcoded upper limit
|
||||||
|
let emojis = db.fetch_emoji_by_parent_id(&server.id).await?;
|
||||||
|
if emojis.len() > 99 {
|
||||||
|
return Err(Error::TooManyEmoji);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EmojiParent::Detached => return Err(Error::InvalidOperation),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Find the relevant attachment
|
||||||
|
let attachment = File::use_emoji(db, &id, &id).await?;
|
||||||
|
|
||||||
|
// Create the emoji object
|
||||||
|
let emoji = Emoji {
|
||||||
|
id,
|
||||||
|
parent: data.parent,
|
||||||
|
creator_id: user.id,
|
||||||
|
name: data.name,
|
||||||
|
animated: "image/gif" == &attachment.content_type,
|
||||||
|
nsfw: data.nsfw,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Save emoji
|
||||||
|
emoji.create(db).await?;
|
||||||
|
Ok(Json(emoji))
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
use revolt_quark::models::{emoji::EmojiParent, User};
|
||||||
|
use revolt_quark::{perms, Db, EmptyResponse, Error, Permission, Ref, Result};
|
||||||
|
|
||||||
|
/// # Delete Emoji
|
||||||
|
///
|
||||||
|
/// Delete an emoji by its id.
|
||||||
|
#[openapi(tag = "Emojis")]
|
||||||
|
#[delete("/emoji/<id>")]
|
||||||
|
pub async fn delete_emoji(db: &Db, user: User, id: Ref) -> Result<EmptyResponse> {
|
||||||
|
// Bots cannot manage emoji
|
||||||
|
if user.bot.is_some() {
|
||||||
|
return Err(Error::IsBot);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch the emoji
|
||||||
|
let emoji = id.as_emoji(db).await?;
|
||||||
|
|
||||||
|
// If we uploaded the emoji, then we have permission to delete it
|
||||||
|
if emoji.creator_id != user.id {
|
||||||
|
// Otherwise, validate we have permission to delete from parent
|
||||||
|
match &emoji.parent {
|
||||||
|
EmojiParent::Server { id } => {
|
||||||
|
let server = db.fetch_server(id).await?;
|
||||||
|
|
||||||
|
// Check for permission
|
||||||
|
perms(&user)
|
||||||
|
.server(&server)
|
||||||
|
.throw_permission(db, Permission::ManageCustomisation)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
EmojiParent::Detached => return Ok(EmptyResponse),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the emoji
|
||||||
|
emoji.delete(db).await.map(|_| EmptyResponse)
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
use revolt_quark::models::{Emoji, User};
|
||||||
|
use revolt_quark::{Db, Ref, Result};
|
||||||
|
|
||||||
|
use rocket::serde::json::Json;
|
||||||
|
|
||||||
|
/// # Fetch Emoji
|
||||||
|
///
|
||||||
|
/// Fetch an emoji by its id.
|
||||||
|
#[openapi(tag = "Emojis")]
|
||||||
|
#[get("/emoji/<id>")]
|
||||||
|
pub async fn fetch_emoji(db: &Db, _user: User, id: Ref) -> Result<Json<Emoji>> {
|
||||||
|
id.as_emoji(db).await.map(Json)
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
use rocket::Route;
|
||||||
|
use rocket_okapi::okapi::openapi3::OpenApi;
|
||||||
|
|
||||||
|
mod emoji_create;
|
||||||
|
mod emoji_delete;
|
||||||
|
mod emoji_fetch;
|
||||||
|
|
||||||
|
pub fn routes() -> (Vec<Route>, OpenApi) {
|
||||||
|
openapi_get_routes_spec![
|
||||||
|
emoji_create::create_emoji,
|
||||||
|
emoji_delete::delete_emoji,
|
||||||
|
emoji_fetch::fetch_emoji
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ use rocket_okapi::{okapi::openapi3::OpenApi, settings::OpenApiSettings};
|
|||||||
|
|
||||||
mod bots;
|
mod bots;
|
||||||
mod channels;
|
mod channels;
|
||||||
|
mod customisation;
|
||||||
mod invites;
|
mod invites;
|
||||||
mod onboard;
|
mod onboard;
|
||||||
mod push;
|
mod push;
|
||||||
@@ -25,8 +26,10 @@ pub fn mount(mut rocket: Rocket<Build>) -> Rocket<Build> {
|
|||||||
"/channels" => channels::routes(),
|
"/channels" => channels::routes(),
|
||||||
"/servers" => servers::routes(),
|
"/servers" => servers::routes(),
|
||||||
"/invites" => invites::routes(),
|
"/invites" => invites::routes(),
|
||||||
"/auth/account" => rauth::web::account::routes(),
|
"/custom" => customisation::routes(),
|
||||||
"/auth/session" => rauth::web::session::routes(),
|
"/auth/account" => rocket_rauth::routes::account::routes(),
|
||||||
|
"/auth/session" => rocket_rauth::routes::session::routes(),
|
||||||
|
"/auth/mfa" => rocket_rauth::routes::mfa::routes(),
|
||||||
"/onboard" => onboard::routes(),
|
"/onboard" => onboard::routes(),
|
||||||
"/push" => push::routes(),
|
"/push" => push::routes(),
|
||||||
"/sync" => sync::routes(),
|
"/sync" => sync::routes(),
|
||||||
@@ -77,6 +80,7 @@ fn custom_openapi_spec() -> OpenApi {
|
|||||||
"Channel Invites",
|
"Channel Invites",
|
||||||
"Channel Permissions",
|
"Channel Permissions",
|
||||||
"Messaging",
|
"Messaging",
|
||||||
|
"Interactions",
|
||||||
"Groups",
|
"Groups",
|
||||||
"Voice"
|
"Voice"
|
||||||
]
|
]
|
||||||
@@ -95,12 +99,19 @@ fn custom_openapi_spec() -> OpenApi {
|
|||||||
"Invites"
|
"Invites"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Customisation",
|
||||||
|
"tags": [
|
||||||
|
"Emojis"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "Authentication",
|
"name": "Authentication",
|
||||||
"tags": [
|
"tags": [
|
||||||
"Account",
|
"Account",
|
||||||
"Session",
|
"Session",
|
||||||
"Onboarding"
|
"Onboarding",
|
||||||
|
"MFA"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -117,7 +128,7 @@ fn custom_openapi_spec() -> OpenApi {
|
|||||||
openapi: OpenApi::default_version(),
|
openapi: OpenApi::default_version(),
|
||||||
info: Info {
|
info: Info {
|
||||||
title: "Revolt API".to_owned(),
|
title: "Revolt API".to_owned(),
|
||||||
description: Some("User-first privacy focused chat platform.".to_owned()),
|
description: Some("Open source user-first chat platform.".to_owned()),
|
||||||
terms_of_service: Some("https://revolt.chat/terms".to_owned()),
|
terms_of_service: Some("https://revolt.chat/terms".to_owned()),
|
||||||
contact: Some(Contact {
|
contact: Some(Contact {
|
||||||
name: Some("Revolt Support".to_owned()),
|
name: Some("Revolt Support".to_owned()),
|
||||||
@@ -130,7 +141,7 @@ fn custom_openapi_spec() -> OpenApi {
|
|||||||
url: Some("https://github.com/revoltchat/delta/blob/master/LICENSE".to_owned()),
|
url: Some("https://github.com/revoltchat/delta/blob/master/LICENSE".to_owned()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
version: "0.5.3-rc.1".to_owned(),
|
version: env!("CARGO_PKG_VERSION").to_string(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
servers: vec![
|
servers: vec![
|
||||||
@@ -242,6 +253,11 @@ fn custom_openapi_spec() -> OpenApi {
|
|||||||
description: Some("Create and manage sessions".to_owned()),
|
description: Some("Create and manage sessions".to_owned()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
|
Tag {
|
||||||
|
name: "MFA".to_owned(),
|
||||||
|
description: Some("Multi-factor Authentication".to_owned()),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
Tag {
|
Tag {
|
||||||
name: "Onboarding".to_owned(),
|
name: "Onboarding".to_owned(),
|
||||||
description: Some(
|
description: Some(
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
use crate::util::regex::RE_USERNAME;
|
use crate::util::regex::RE_USERNAME;
|
||||||
use revolt_quark::{models::User, Database, EmptyResponse, Error, Result};
|
use revolt_quark::{models::User, rauth::models::Session, Database, EmptyResponse, Error, Result};
|
||||||
|
|
||||||
use rauth::entities::Session;
|
|
||||||
use rocket::{serde::json::Json, State};
|
use rocket::{serde::json::Json, State};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use validator::Validate;
|
use validator::Validate;
|
||||||
@@ -33,13 +32,10 @@ pub async fn req(
|
|||||||
data.validate()
|
data.validate()
|
||||||
.map_err(|error| Error::FailedValidation { error })?;
|
.map_err(|error| Error::FailedValidation { error })?;
|
||||||
|
|
||||||
if db.is_username_taken(&data.username).await? {
|
let username = User::validate_username(db, data.username).await?;
|
||||||
return Err(Error::UsernameTaken);
|
|
||||||
}
|
|
||||||
|
|
||||||
let user = User {
|
let user = User {
|
||||||
id: session.user_id,
|
id: session.user_id,
|
||||||
username: data.username,
|
username,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
use rauth::entities::Session;
|
use revolt_quark::{models::User, rauth::models::Session};
|
||||||
use revolt_quark::models::User;
|
|
||||||
use rocket::serde::json::Json;
|
use rocket::serde::json::Json;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
use revolt_quark::{EmptyResponse, Error, Result};
|
use revolt_quark::{
|
||||||
|
rauth::{
|
||||||
use rauth::{
|
models::{Session, WebPushSubscription},
|
||||||
entities::{Model, Session, WebPushSubscription},
|
RAuth,
|
||||||
logic::Auth,
|
},
|
||||||
|
EmptyResponse, Error, Result,
|
||||||
};
|
};
|
||||||
|
|
||||||
use rocket::{serde::json::Json, State};
|
use rocket::{serde::json::Json, State};
|
||||||
|
|
||||||
/// # Push Subscribe
|
/// # Push Subscribe
|
||||||
@@ -14,13 +16,13 @@ use rocket::{serde::json::Json, State};
|
|||||||
#[openapi(tag = "Web Push")]
|
#[openapi(tag = "Web Push")]
|
||||||
#[post("/subscribe", data = "<data>")]
|
#[post("/subscribe", data = "<data>")]
|
||||||
pub async fn req(
|
pub async fn req(
|
||||||
auth: &State<Auth>,
|
rauth: &State<RAuth>,
|
||||||
mut session: Session,
|
mut session: Session,
|
||||||
data: Json<WebPushSubscription>,
|
data: Json<WebPushSubscription>,
|
||||||
) -> Result<EmptyResponse> {
|
) -> Result<EmptyResponse> {
|
||||||
session.subscription = Some(data.into_inner());
|
session.subscription = Some(data.into_inner());
|
||||||
session
|
session
|
||||||
.save(&auth.db, None)
|
.save(&rauth)
|
||||||
.await
|
.await
|
||||||
.map(|_| EmptyResponse)
|
.map(|_| EmptyResponse)
|
||||||
.map_err(|_| Error::DatabaseError {
|
.map_err(|_| Error::DatabaseError {
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
use revolt_quark::{EmptyResponse, Error, Result};
|
use revolt_quark::{
|
||||||
|
rauth::{models::Session, RAuth},
|
||||||
use rauth::{
|
EmptyResponse, Error, Result,
|
||||||
entities::{Model, Session},
|
|
||||||
logic::Auth,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use rocket::State;
|
use rocket::State;
|
||||||
|
|
||||||
/// # Unsubscribe
|
/// # Unsubscribe
|
||||||
@@ -11,10 +10,10 @@ use rocket::State;
|
|||||||
/// Remove the Web Push subscription associated with the current session.
|
/// Remove the Web Push subscription associated with the current session.
|
||||||
#[openapi(tag = "Web Push")]
|
#[openapi(tag = "Web Push")]
|
||||||
#[post("/unsubscribe")]
|
#[post("/unsubscribe")]
|
||||||
pub async fn req(auth: &State<Auth>, mut session: Session) -> Result<EmptyResponse> {
|
pub async fn req(rauth: &State<RAuth>, mut session: Session) -> Result<EmptyResponse> {
|
||||||
session.subscription = None;
|
session.subscription = None;
|
||||||
session
|
session
|
||||||
.save(&auth.db, None)
|
.save(&rauth)
|
||||||
.await
|
.await
|
||||||
.map(|_| EmptyResponse)
|
.map(|_| EmptyResponse)
|
||||||
.map_err(|_| Error::DatabaseError {
|
.map_err(|_| Error::DatabaseError {
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ pub struct RevoltConfig {
|
|||||||
#[get("/")]
|
#[get("/")]
|
||||||
pub async fn root() -> Result<Json<RevoltConfig>> {
|
pub async fn root() -> Result<Json<RevoltConfig>> {
|
||||||
Ok(Json(RevoltConfig {
|
Ok(Json(RevoltConfig {
|
||||||
revolt: crate::version::VERSION.to_string(),
|
revolt: env!("CARGO_PKG_VERSION").to_string(),
|
||||||
features: RevoltFeatures {
|
features: RevoltFeatures {
|
||||||
captcha: CaptchaFeature {
|
captcha: CaptchaFeature {
|
||||||
enabled: *USE_HCAPTCHA,
|
enabled: *USE_HCAPTCHA,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use revolt_quark::{
|
use revolt_quark::{
|
||||||
models::{ServerBan, User},
|
models::{server_member::MemberCompositeKey, ServerBan, User},
|
||||||
perms, Db, Error, Permission, Ref, Result,
|
perms, Db, Error, Permission, Ref, Result,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -47,8 +47,8 @@ pub async fn req(
|
|||||||
.throw_permission(db, Permission::BanMembers)
|
.throw_permission(db, Permission::BanMembers)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let member = target.as_member(db, &server.id).await?;
|
// If member exists, check privileges against them
|
||||||
|
if let Ok(member) = target.as_member(db, &server.id).await {
|
||||||
if member.get_ranking(permissions.server.get().unwrap())
|
if member.get_ranking(permissions.server.get().unwrap())
|
||||||
<= permissions.get_member_rank().unwrap_or(i64::MIN)
|
<= permissions.get_member_rank().unwrap_or(i64::MIN)
|
||||||
{
|
{
|
||||||
@@ -56,4 +56,18 @@ pub async fn req(
|
|||||||
}
|
}
|
||||||
|
|
||||||
server.ban_member(db, member, data.reason).await.map(Json)
|
server.ban_member(db, member, data.reason).await.map(Json)
|
||||||
|
} else {
|
||||||
|
let server_id = server.id.to_string();
|
||||||
|
server
|
||||||
|
.ban_user(
|
||||||
|
db,
|
||||||
|
MemberCompositeKey {
|
||||||
|
server: server_id,
|
||||||
|
user: target.id,
|
||||||
|
},
|
||||||
|
data.reason,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map(Json)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
use revolt_quark::models::{Emoji, User};
|
||||||
|
use revolt_quark::{perms, Db, Ref, Result};
|
||||||
|
|
||||||
|
use rocket::serde::json::Json;
|
||||||
|
|
||||||
|
/// # Fetch Server Emoji
|
||||||
|
///
|
||||||
|
/// Fetch all emoji on a server.
|
||||||
|
#[openapi(tag = "Server Customisation")]
|
||||||
|
#[get("/<target>/emojis")]
|
||||||
|
pub async fn list_emoji(db: &Db, user: User, target: Ref) -> Result<Json<Vec<Emoji>>> {
|
||||||
|
let server = target.as_server(db).await?;
|
||||||
|
perms(&user).server(&server).calc(db).await?;
|
||||||
|
|
||||||
|
// Fetch all emoji from server if we can view it
|
||||||
|
db.fetch_emoji_by_parent_id(&server.id).await.map(Json)
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ use revolt_quark::{
|
|||||||
server_member::{FieldsMember, PartialMember},
|
server_member::{FieldsMember, PartialMember},
|
||||||
File, Member, User,
|
File, Member, User,
|
||||||
},
|
},
|
||||||
perms, Db, Error, Permission, Ref, Result,
|
perms, Db, Error, Permission, Ref, Result, Timestamp,
|
||||||
};
|
};
|
||||||
|
|
||||||
use rocket::serde::json::Json;
|
use rocket::serde::json::Json;
|
||||||
@@ -22,6 +22,8 @@ pub struct DataMemberEdit {
|
|||||||
avatar: Option<String>,
|
avatar: Option<String>,
|
||||||
/// Array of role ids
|
/// Array of role ids
|
||||||
roles: Option<Vec<String>>,
|
roles: Option<Vec<String>>,
|
||||||
|
/// Timestamp this member is timed out until
|
||||||
|
timeout: Option<Timestamp>,
|
||||||
/// Fields to remove from channel object
|
/// Fields to remove from channel object
|
||||||
#[validate(length(min = 1))]
|
#[validate(length(min = 1))]
|
||||||
remove: Option<Vec<FieldsMember>>,
|
remove: Option<Vec<FieldsMember>>,
|
||||||
@@ -89,6 +91,16 @@ pub async fn req(
|
|||||||
required.push(Permission::AssignRoles);
|
required.push(Permission::AssignRoles);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if data.timeout.is_some()
|
||||||
|
|| data
|
||||||
|
.remove
|
||||||
|
.as_ref()
|
||||||
|
.map(|x| x.contains(&FieldsMember::Timeout))
|
||||||
|
.unwrap_or_default()
|
||||||
|
{
|
||||||
|
required.push(Permission::TimeoutMembers);
|
||||||
|
}
|
||||||
|
|
||||||
for permission in required {
|
for permission in required {
|
||||||
permissions.throw_permission(db, permission).await?;
|
permissions.throw_permission(db, permission).await?;
|
||||||
}
|
}
|
||||||
@@ -105,13 +117,7 @@ pub async fn req(
|
|||||||
|
|
||||||
// Check permissions against roles in diff
|
// Check permissions against roles in diff
|
||||||
if let Some(roles) = &data.roles {
|
if let Some(roles) = &data.roles {
|
||||||
let fallback = vec![];
|
let current_roles = member.roles.iter().collect::<HashSet<&String>>();
|
||||||
let current_roles = member
|
|
||||||
.roles
|
|
||||||
.as_ref()
|
|
||||||
.unwrap_or(&fallback)
|
|
||||||
.iter()
|
|
||||||
.collect::<HashSet<&String>>();
|
|
||||||
|
|
||||||
let new_roles = roles.iter().collect::<HashSet<&String>>();
|
let new_roles = roles.iter().collect::<HashSet<&String>>();
|
||||||
let added_roles: Vec<&&String> = new_roles.difference(¤t_roles).collect();
|
let added_roles: Vec<&&String> = new_roles.difference(¤t_roles).collect();
|
||||||
@@ -132,12 +138,14 @@ pub async fn req(
|
|||||||
nickname,
|
nickname,
|
||||||
avatar,
|
avatar,
|
||||||
roles,
|
roles,
|
||||||
|
timeout,
|
||||||
remove,
|
remove,
|
||||||
} = data;
|
} = data;
|
||||||
|
|
||||||
let mut partial = PartialMember {
|
let mut partial = PartialMember {
|
||||||
nickname,
|
nickname,
|
||||||
roles,
|
roles,
|
||||||
|
timeout,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ pub async fn req(db: &Db, user: User, target: Ref, member: Ref) -> Result<EmptyR
|
|||||||
}
|
}
|
||||||
|
|
||||||
server
|
server
|
||||||
.remove_member(db, member, RemovalIntention::Kick)
|
.remove_member(db, member, RemovalIntention::Kick, false)
|
||||||
.await
|
.await
|
||||||
.map(|_| EmptyResponse)
|
.map(|_| EmptyResponse)
|
||||||
}
|
}
|
||||||
@@ -1,30 +1,26 @@
|
|||||||
use rocket::Route;
|
use rocket::Route;
|
||||||
use rocket_okapi::okapi::openapi3::OpenApi;
|
use rocket_okapi::okapi::openapi3::OpenApi;
|
||||||
|
|
||||||
mod server_ack;
|
|
||||||
mod server_create;
|
|
||||||
mod server_delete;
|
|
||||||
mod server_edit;
|
|
||||||
mod server_fetch;
|
|
||||||
|
|
||||||
mod channel_create;
|
|
||||||
|
|
||||||
mod member_edit;
|
|
||||||
mod member_fetch;
|
|
||||||
mod member_fetch_all;
|
|
||||||
mod member_remove;
|
|
||||||
|
|
||||||
mod ban_create;
|
mod ban_create;
|
||||||
mod ban_list;
|
mod ban_list;
|
||||||
mod ban_remove;
|
mod ban_remove;
|
||||||
|
mod channel_create;
|
||||||
|
mod emoji_list;
|
||||||
mod invites_fetch;
|
mod invites_fetch;
|
||||||
|
mod member_edit;
|
||||||
|
mod member_fetch;
|
||||||
|
mod member_fetch_all;
|
||||||
|
mod member_remove;
|
||||||
mod permissions_set;
|
mod permissions_set;
|
||||||
mod permissions_set_default;
|
mod permissions_set_default;
|
||||||
mod roles_create;
|
mod roles_create;
|
||||||
mod roles_delete;
|
mod roles_delete;
|
||||||
mod roles_edit;
|
mod roles_edit;
|
||||||
|
mod server_ack;
|
||||||
|
mod server_create;
|
||||||
|
mod server_delete;
|
||||||
|
mod server_edit;
|
||||||
|
mod server_fetch;
|
||||||
|
|
||||||
pub fn routes() -> (Vec<Route>, OpenApi) {
|
pub fn routes() -> (Vec<Route>, OpenApi) {
|
||||||
openapi_get_routes_spec![
|
openapi_get_routes_spec![
|
||||||
@@ -46,6 +42,7 @@ pub fn routes() -> (Vec<Route>, OpenApi) {
|
|||||||
roles_edit::req,
|
roles_edit::req,
|
||||||
roles_delete::req,
|
roles_delete::req,
|
||||||
permissions_set::req,
|
permissions_set::req,
|
||||||
permissions_set_default::req
|
permissions_set_default::req,
|
||||||
|
emoji_list::list_emoji
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -3,7 +3,9 @@ use revolt_quark::{
|
|||||||
server::{FieldsRole, PartialRole, Role},
|
server::{FieldsRole, PartialRole, Role},
|
||||||
User,
|
User,
|
||||||
},
|
},
|
||||||
perms, Db, Error, Permission, Ref, Result,
|
perms,
|
||||||
|
util::regex::RE_COLOUR,
|
||||||
|
Db, Error, Permission, Ref, Result,
|
||||||
};
|
};
|
||||||
|
|
||||||
use rocket::serde::json::Json;
|
use rocket::serde::json::Json;
|
||||||
@@ -17,7 +19,7 @@ pub struct DataEditRole {
|
|||||||
#[validate(length(min = 1, max = 32))]
|
#[validate(length(min = 1, max = 32))]
|
||||||
name: Option<String>,
|
name: Option<String>,
|
||||||
/// Role colour
|
/// Role colour
|
||||||
#[validate(length(min = 1, max = 32))]
|
#[validate(length(min = 1, max = 128), regex = "RE_COLOUR")]
|
||||||
colour: Option<String>,
|
colour: Option<String>,
|
||||||
/// Whether this role should be displayed separately
|
/// Whether this role should be displayed separately
|
||||||
hoist: Option<bool>,
|
hoist: Option<bool>,
|
||||||
|
|||||||
@@ -2,13 +2,27 @@ use revolt_quark::{
|
|||||||
models::{server_member::RemovalIntention, User},
|
models::{server_member::RemovalIntention, User},
|
||||||
Db, EmptyResponse, Ref, Result,
|
Db, EmptyResponse, Ref, Result,
|
||||||
};
|
};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use validator::Validate;
|
||||||
|
|
||||||
|
/// # Query Parameters
|
||||||
|
#[derive(Validate, Serialize, Deserialize, JsonSchema, FromForm)]
|
||||||
|
pub struct OptionsServerDelete {
|
||||||
|
/// Whether to not send a leave message
|
||||||
|
leave_silently: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
/// # Delete / Leave Server
|
/// # Delete / Leave Server
|
||||||
///
|
///
|
||||||
/// Deletes a server if owner otherwise leaves.
|
/// Deletes a server if owner otherwise leaves.
|
||||||
#[openapi(tag = "Server Information")]
|
#[openapi(tag = "Server Information")]
|
||||||
#[delete("/<target>")]
|
#[delete("/<target>?<options..>")]
|
||||||
pub async fn req(db: &Db, user: User, target: Ref) -> Result<EmptyResponse> {
|
pub async fn req(
|
||||||
|
db: &Db,
|
||||||
|
user: User,
|
||||||
|
target: Ref,
|
||||||
|
options: OptionsServerDelete,
|
||||||
|
) -> Result<EmptyResponse> {
|
||||||
let server = target.as_server(db).await?;
|
let server = target.as_server(db).await?;
|
||||||
let member = db.fetch_member(&target.id, &user.id).await?;
|
let member = db.fetch_member(&target.id, &user.id).await?;
|
||||||
|
|
||||||
@@ -16,7 +30,12 @@ pub async fn req(db: &Db, user: User, target: Ref) -> Result<EmptyResponse> {
|
|||||||
server.delete(db).await
|
server.delete(db).await
|
||||||
} else {
|
} else {
|
||||||
server
|
server
|
||||||
.remove_member(db, member, RemovalIntention::Leave)
|
.remove_member(
|
||||||
|
db,
|
||||||
|
member,
|
||||||
|
RemovalIntention::Leave,
|
||||||
|
options.leave_silently.unwrap_or_default(),
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
.map(|_| EmptyResponse)
|
.map(|_| EmptyResponse)
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use revolt_quark::{
|
use revolt_quark::{
|
||||||
models::{
|
models::{
|
||||||
server::{Category, FieldsServer, PartialServer, SystemMessageChannels},
|
server::{Category, FieldsServer, PartialServer, SystemMessageChannels},
|
||||||
@@ -33,6 +35,8 @@ pub struct DataEditServer {
|
|||||||
|
|
||||||
// Whether this server is age-restricted
|
// Whether this server is age-restricted
|
||||||
// nsfw: Option<bool>,
|
// nsfw: Option<bool>,
|
||||||
|
/// Whether this server is public and should show up on [Revolt Discover](https://rvlt.gg)
|
||||||
|
discoverable: Option<bool>,
|
||||||
/// Whether analytics should be collected for this server
|
/// Whether analytics should be collected for this server
|
||||||
///
|
///
|
||||||
/// Must be enabled in order to show up on [Revolt Discover](https://rvlt.gg).
|
/// Must be enabled in order to show up on [Revolt Discover](https://rvlt.gg).
|
||||||
@@ -102,6 +106,7 @@ pub async fn req(
|
|||||||
categories,
|
categories,
|
||||||
system_messages,
|
system_messages,
|
||||||
// nsfw,
|
// nsfw,
|
||||||
|
discoverable,
|
||||||
analytics,
|
analytics,
|
||||||
remove,
|
remove,
|
||||||
} = data;
|
} = data;
|
||||||
@@ -112,6 +117,7 @@ pub async fn req(
|
|||||||
categories,
|
categories,
|
||||||
system_messages,
|
system_messages,
|
||||||
// nsfw,
|
// nsfw,
|
||||||
|
discoverable,
|
||||||
analytics,
|
analytics,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
@@ -131,29 +137,44 @@ pub async fn req(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Apply new icon
|
// 2. Validate changes
|
||||||
|
if let Some(system_messages) = &partial.system_messages {
|
||||||
|
for id in system_messages.clone().into_channel_ids() {
|
||||||
|
if !server.channels.contains(&id) {
|
||||||
|
return Err(Error::NotFound);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(categories) = &mut partial.categories {
|
||||||
|
let mut channel_ids = HashSet::new();
|
||||||
|
for category in categories {
|
||||||
|
for channel in &category.channels {
|
||||||
|
if channel_ids.contains(channel) {
|
||||||
|
return Err(Error::InvalidOperation);
|
||||||
|
}
|
||||||
|
|
||||||
|
channel_ids.insert(channel.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
category
|
||||||
|
.channels
|
||||||
|
.retain(|item| server.channels.contains(item));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Apply new icon
|
||||||
if let Some(icon) = icon {
|
if let Some(icon) = icon {
|
||||||
partial.icon = Some(File::use_server_icon(db, &icon, &server.id).await?);
|
partial.icon = Some(File::use_server_icon(db, &icon, &server.id).await?);
|
||||||
server.icon = partial.icon.clone();
|
server.icon = partial.icon.clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Apply new banner
|
// 4. Apply new banner
|
||||||
if let Some(banner) = banner {
|
if let Some(banner) = banner {
|
||||||
partial.banner = Some(File::use_banner(db, &banner, &server.id).await?);
|
partial.banner = Some(File::use_banner(db, &banner, &server.id).await?);
|
||||||
server.banner = partial.banner.clone();
|
server.banner = partial.banner.clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Validate changes
|
|
||||||
if let Some(system_messages) = &partial.system_messages {
|
|
||||||
let channels = system_messages.clone().into_channel_ids();
|
|
||||||
if !db
|
|
||||||
.check_channels_exist(&channels.into_iter().collect())
|
|
||||||
.await?
|
|
||||||
{
|
|
||||||
return Err(Error::NotFound);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
server
|
server
|
||||||
.update(db, partial, remove.unwrap_or_default())
|
.update(db, partial, remove.unwrap_or_default())
|
||||||
.await?;
|
.await?;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use revolt_quark::models::User;
|
use revolt_quark::models::User;
|
||||||
use revolt_quark::r#impl::generic::users::user_settings::UserSettingsImpl;
|
use revolt_quark::r#impl::UserSettingsImpl;
|
||||||
use revolt_quark::{Db, EmptyResponse, Result};
|
use revolt_quark::{Db, EmptyResponse, Result};
|
||||||
|
|
||||||
use chrono::prelude::*;
|
use chrono::prelude::*;
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
use revolt_quark::models::User;
|
use revolt_quark::models::User;
|
||||||
use revolt_quark::{Database, Error, Result};
|
use revolt_quark::{Database, Error, Ref, Result};
|
||||||
|
|
||||||
use rocket::serde::json::Json;
|
use rocket::serde::json::Json;
|
||||||
use rocket::State;
|
use rocket::State;
|
||||||
|
|
||||||
/// # Send Friend Request / Accept Request
|
/// # Accept Friend Request
|
||||||
///
|
///
|
||||||
/// Send a friend request to another user or accept another user's friend request.
|
/// Accept another user's friend request.
|
||||||
#[openapi(tag = "Relationships")]
|
#[openapi(tag = "Relationships")]
|
||||||
#[put("/<username>/friend")]
|
#[put("/<target>/friend")]
|
||||||
pub async fn req(db: &State<Database>, user: User, username: String) -> Result<Json<User>> {
|
pub async fn req(db: &State<Database>, user: User, target: Ref) -> Result<Json<User>> {
|
||||||
let mut target = db.fetch_user_by_username(&username).await?;
|
let mut target = target.as_user(db).await?;
|
||||||
|
|
||||||
if user.bot.is_some() || target.bot.is_some() {
|
if user.bot.is_some() || target.bot.is_some() {
|
||||||
return Err(Error::IsBot);
|
return Err(Error::IsBot);
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
use crate::util::regex::RE_USERNAME;
|
use crate::util::regex::RE_USERNAME;
|
||||||
use rauth::entities::Account;
|
use revolt_quark::{models::User, rauth::models::Account, Database, Error, Result};
|
||||||
use revolt_quark::{models::User, Database, Error, Result};
|
|
||||||
use rocket::{serde::json::Json, State};
|
use rocket::{serde::json::Json, State};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use validator::Validate;
|
use validator::Validate;
|
||||||
|
|||||||
@@ -59,18 +59,6 @@ impl rocket_okapi::response::OpenApiResponderInner for CachedFile {
|
|||||||
pub async fn req(target: String) -> CachedFile {
|
pub async fn req(target: String) -> CachedFile {
|
||||||
CachedFile((
|
CachedFile((
|
||||||
ContentType::PNG,
|
ContentType::PNG,
|
||||||
match target.chars().last().unwrap() {
|
revolt_quark::util::pfp::avatar(target.chars().last().unwrap()),
|
||||||
// 0123456789ABCDEFGHJKMNPQRSTVWXYZ
|
|
||||||
'0' | '1' | '2' | '3' | 'S' | 'Z' => {
|
|
||||||
include_bytes!(crate::asset!("user/2.png")).to_vec()
|
|
||||||
}
|
|
||||||
'4' | '5' | '6' | '7' | 'T' => include_bytes!(crate::asset!("user/3.png")).to_vec(),
|
|
||||||
'8' | '9' | 'A' | 'B' => include_bytes!(crate::asset!("user/4.png")).to_vec(),
|
|
||||||
'C' | 'D' | 'E' | 'F' | 'V' => include_bytes!(crate::asset!("user/5.png")).to_vec(),
|
|
||||||
'G' | 'H' | 'J' | 'K' | 'W' => include_bytes!(crate::asset!("user/6.png")).to_vec(),
|
|
||||||
'M' | 'N' | 'P' | 'Q' | 'X' => include_bytes!(crate::asset!("user/7.png")).to_vec(),
|
|
||||||
/*'0' | '1' | '2' | '3' | 'R' | 'Y'*/
|
|
||||||
_ => include_bytes!(crate::asset!("user/1.png")).to_vec(),
|
|
||||||
},
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -13,6 +13,7 @@ mod find_mutual;
|
|||||||
mod get_default_avatar;
|
mod get_default_avatar;
|
||||||
mod open_dm;
|
mod open_dm;
|
||||||
mod remove_friend;
|
mod remove_friend;
|
||||||
|
mod send_friend_request;
|
||||||
mod unblock_user;
|
mod unblock_user;
|
||||||
|
|
||||||
pub fn routes() -> (Vec<Route>, OpenApi) {
|
pub fn routes() -> (Vec<Route>, OpenApi) {
|
||||||
@@ -33,5 +34,6 @@ pub fn routes() -> (Vec<Route>, OpenApi) {
|
|||||||
remove_friend::req,
|
remove_friend::req,
|
||||||
block_user::req,
|
block_user::req,
|
||||||
unblock_user::req,
|
unblock_user::req,
|
||||||
|
send_friend_request::req,
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
use revolt_quark::models::User;
|
||||||
|
use revolt_quark::{Database, Error, Result};
|
||||||
|
|
||||||
|
use rocket::serde::json::Json;
|
||||||
|
use rocket::State;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
/// # User Lookup Information
|
||||||
|
#[derive(Serialize, Deserialize, JsonSchema)]
|
||||||
|
pub struct DataSendFriendRequest {
|
||||||
|
username: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Send Friend Request
|
||||||
|
///
|
||||||
|
/// Send a friend request to another user.
|
||||||
|
#[openapi(tag = "Relationships")]
|
||||||
|
#[post("/friend", data = "<data>")]
|
||||||
|
pub async fn req(
|
||||||
|
db: &State<Database>,
|
||||||
|
user: User,
|
||||||
|
data: Json<DataSendFriendRequest>,
|
||||||
|
) -> Result<Json<User>> {
|
||||||
|
let mut target = db.fetch_user_by_username(&data.username).await?;
|
||||||
|
|
||||||
|
if user.bot.is_some() || target.bot.is_some() {
|
||||||
|
return Err(Error::IsBot);
|
||||||
|
}
|
||||||
|
|
||||||
|
user.add_friend(db, &mut target).await?;
|
||||||
|
Ok(Json(target.with_auto_perspective(db, &user).await))
|
||||||
|
}
|
||||||
@@ -1,3 +1 @@
|
|||||||
pub mod idempotency;
|
|
||||||
pub mod ratelimiter;
|
|
||||||
pub mod regex;
|
pub mod regex;
|
||||||
@@ -6,4 +6,9 @@ use regex::Regex;
|
|||||||
/// Block zero width space
|
/// Block zero width space
|
||||||
/// Block lookalike characters
|
/// Block lookalike characters
|
||||||
pub static RE_USERNAME: Lazy<Regex> =
|
pub static RE_USERNAME: Lazy<Regex> =
|
||||||
Lazy::new(|| Regex::new(r"^[^\u200BА-Яа-яΑ-Ωα-ω]+$").unwrap());
|
Lazy::new(|| Regex::new(r"^[^\u200BА-Яа-яΑ-Ωα-ω@#:\n\r\[\]]+$").unwrap());
|
||||||
|
|
||||||
|
/// Regex for valid emoji names
|
||||||
|
///
|
||||||
|
/// Alphanumeric and underscores
|
||||||
|
pub static RE_EMOJI: Lazy<Regex> = Lazy::new(|| Regex::new(r"^[a-z0-9_]+$").unwrap());
|
||||||
@@ -1 +0,0 @@
|
|||||||
pub const VERSION: &str = "0.5.3-5-patch.2";
|
|
||||||
+28
-8
@@ -8,8 +8,20 @@ edition = "2021"
|
|||||||
|
|
||||||
[features]
|
[features]
|
||||||
mongo = [ "mongodb" ]
|
mongo = [ "mongodb" ]
|
||||||
rocket_impl = [ "rocket", "rocket_empty" ]
|
rocket_impl = [
|
||||||
test = [ "async-std", "mongo", "mongodb/async-std-runtime", "rocket_impl", "rauth" ]
|
"rocket",
|
||||||
|
"rocket_empty",
|
||||||
|
"rocket_cors",
|
||||||
|
|
||||||
|
"lru",
|
||||||
|
"dashmap",
|
||||||
|
|
||||||
|
"rauth/database-mongodb",
|
||||||
|
"rauth/rocket_impl",
|
||||||
|
"rauth/okapi_impl"
|
||||||
|
]
|
||||||
|
|
||||||
|
test = [ "async-std", "mongo", "mongodb/async-std-runtime", "rocket_impl" ]
|
||||||
default = [ "test" ]
|
default = [ "test" ]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
@@ -26,8 +38,8 @@ bson = { version = "2.1.0", features = ["chrono-0_4"] }
|
|||||||
|
|
||||||
# Spec Generation
|
# Spec Generation
|
||||||
schemars = "0.8.8"
|
schemars = "0.8.8"
|
||||||
okapi = { git = "https://github.com/insertish/okapi", rev = "dcf0df115596ee07a587a7a543cddf3d7944645b" }
|
okapi = { git = "https://github.com/insertish/okapi", rev = "a1048d0c8cd771e424ec97d33d825c32e06aa120" }
|
||||||
rocket_okapi = { git = "https://github.com/insertish/okapi", rev = "dcf0df115596ee07a587a7a543cddf3d7944645b" }
|
rocket_okapi = { git = "https://github.com/insertish/okapi", rev = "a1048d0c8cd771e424ec97d33d825c32e06aa120" }
|
||||||
# okapi = "0.7.0-rc.1"
|
# okapi = "0.7.0-rc.1"
|
||||||
# rocket_okapi = "0.8.0-rc.1"
|
# rocket_okapi = "0.8.0-rc.1"
|
||||||
|
|
||||||
@@ -52,21 +64,29 @@ regex = "1.5.5"
|
|||||||
nanoid = "0.4.0"
|
nanoid = "0.4.0"
|
||||||
linkify = "0.8.1"
|
linkify = "0.8.1"
|
||||||
dotenv = "0.15.0"
|
dotenv = "0.15.0"
|
||||||
|
indexmap = "1.9.1"
|
||||||
impl_ops = "0.1.1"
|
impl_ops = "0.1.1"
|
||||||
num_enum = "0.5.6"
|
num_enum = "0.5.6"
|
||||||
reqwest = "0.11.10"
|
reqwest = "0.11.10"
|
||||||
bitfield = "0.13.2"
|
bitfield = "0.13.2"
|
||||||
|
once_cell = "1.13.0"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
|
|
||||||
|
lru = { version = "0.7.6", optional = true }
|
||||||
|
dashmap = { version = "5.2.0", optional = true }
|
||||||
|
|
||||||
# Web Push
|
# Web Push
|
||||||
base64 = "0.13.0"
|
base64 = "0.13.0"
|
||||||
web-push = "0.7.2"
|
web-push = "0.7.2"
|
||||||
|
|
||||||
# Implementations
|
# Implementations
|
||||||
rauth = { optional = true, git = "https://github.com/insertish/rauth", rev = "001a9698c56cea79e69e4ae71d7bc2cb48aec1a6" }
|
rocket_http = { optional = true, version = "0.5.0-rc.2" }
|
||||||
rocket = { optional = true, version = "=0.5.0-rc.1", default-features = false, features = ["json"] }
|
rocket = { optional = true, version = "0.5.0-rc.2", default-features = false, features = ["json"] }
|
||||||
rocket_http = { optional = true, version = "=0.5.0-rc.1" }
|
rocket_empty = { optional = true, git = "https://github.com/insertish/rocket_empty", branch = "master" }
|
||||||
rocket_empty = { optional = true, git = "https://github.com/insertish/rocket_empty", branch = "rc1" }
|
rocket_cors = { optional = true, git = "https://github.com/lawliet89/rocket_cors", rev = "5843861a88958c16bfaa0b40f0d8910772bcd2f6" }
|
||||||
|
|
||||||
|
# rAuth
|
||||||
|
rauth = { git = "https://github.com/insertish/rauth", rev = "9c3f9bb58a549a51546bd748274130ac4a667cb4", features = [ "async-std-runtime" ] }
|
||||||
|
|
||||||
# Sentry
|
# Sentry
|
||||||
sentry = "0.25.0"
|
sentry = "0.25.0"
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,5 @@
|
|||||||
|
You requested to have your account deleted, if you did not perform this action please take measures to secure your account immediately.
|
||||||
|
|
||||||
|
Confirm here: {{url}}
|
||||||
|
|
||||||
|
Sent by Revolt.
|
||||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -1,8 +1,7 @@
|
|||||||
use std::env;
|
use std::env;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
|
||||||
use crate::r#impl::dummy::DummyDb;
|
use crate::r#impl::{DummyDb, MongoDb};
|
||||||
use crate::r#impl::mongo::MongoDb;
|
|
||||||
use crate::AbstractDatabase;
|
use crate::AbstractDatabase;
|
||||||
|
|
||||||
/// Database information to use to create a client
|
/// Database information to use to create a client
|
||||||
@@ -61,3 +60,14 @@ impl Deref for Database {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<Database> for rauth::Database {
|
||||||
|
fn from(val: Database) -> Self {
|
||||||
|
match val {
|
||||||
|
Database::Dummy(_) => rauth::Database::default(),
|
||||||
|
Database::MongoDb(MongoDb(client)) => {
|
||||||
|
rauth::Database::MongoDb(rauth::database::MongoDb(client.database("revolt")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ use crate::models::message::{AppendMessage, PartialMessage};
|
|||||||
use crate::models::server::{FieldsRole, FieldsServer, PartialRole, PartialServer};
|
use crate::models::server::{FieldsRole, FieldsServer, PartialRole, PartialServer};
|
||||||
use crate::models::server_member::{FieldsMember, MemberCompositeKey, PartialMember};
|
use crate::models::server_member::{FieldsMember, MemberCompositeKey, PartialMember};
|
||||||
use crate::models::user::{FieldsUser, PartialUser, RelationshipStatus};
|
use crate::models::user::{FieldsUser, PartialUser, RelationshipStatus};
|
||||||
use crate::models::{Channel, Member, Message, Server, User, UserSettings};
|
use crate::models::{Channel, Emoji, Member, Message, Server, User, UserSettings};
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
|
|
||||||
/// WebSocket Client Errors
|
/// WebSocket Client Errors
|
||||||
@@ -52,6 +52,7 @@ pub enum EventV1 {
|
|||||||
servers: Vec<Server>,
|
servers: Vec<Server>,
|
||||||
channels: Vec<Channel>,
|
channels: Vec<Channel>,
|
||||||
members: Vec<Member>,
|
members: Vec<Member>,
|
||||||
|
emojis: Option<Vec<Emoji>>,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Ping response
|
/// Ping response
|
||||||
@@ -77,6 +78,29 @@ pub enum EventV1 {
|
|||||||
/// Delete message
|
/// Delete message
|
||||||
MessageDelete { id: String, channel: String },
|
MessageDelete { id: String, channel: String },
|
||||||
|
|
||||||
|
/// New reaction to a message
|
||||||
|
MessageReact {
|
||||||
|
id: String,
|
||||||
|
channel_id: String,
|
||||||
|
user_id: String,
|
||||||
|
emoji_id: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Remove user's reaction from message
|
||||||
|
MessageUnreact {
|
||||||
|
id: String,
|
||||||
|
channel_id: String,
|
||||||
|
user_id: String,
|
||||||
|
emoji_id: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Remove a reaction from message
|
||||||
|
MessageRemoveReaction {
|
||||||
|
id: String,
|
||||||
|
channel_id: String,
|
||||||
|
emoji_id: String,
|
||||||
|
},
|
||||||
|
|
||||||
/// Bulk delete messages
|
/// Bulk delete messages
|
||||||
BulkMessageDelete { channel: String, ids: Vec<String> },
|
BulkMessageDelete { channel: String, ids: Vec<String> },
|
||||||
|
|
||||||
@@ -170,4 +194,10 @@ pub enum EventV1 {
|
|||||||
|
|
||||||
/// Settings updated remotely
|
/// Settings updated remotely
|
||||||
UserSettingsUpdate { id: String, update: UserSettings },
|
UserSettingsUpdate { id: String, update: UserSettings },
|
||||||
|
|
||||||
|
/// New emoji
|
||||||
|
EmojiCreate(Emoji),
|
||||||
|
|
||||||
|
/// Delete emoji
|
||||||
|
EmojiDelete { id: String },
|
||||||
}
|
}
|
||||||
@@ -111,6 +111,7 @@ impl State {
|
|||||||
|
|
||||||
// Fetch all memberships with their corresponding servers.
|
// Fetch all memberships with their corresponding servers.
|
||||||
let members: Vec<Member> = db.fetch_all_memberships(&user.id).await?;
|
let members: Vec<Member> = db.fetch_all_memberships(&user.id).await?;
|
||||||
|
|
||||||
let server_ids: Vec<String> = members.iter().map(|x| x.id.server.clone()).collect();
|
let server_ids: Vec<String> = members.iter().map(|x| x.id.server.clone()).collect();
|
||||||
let servers = db.fetch_servers(&server_ids).await?;
|
let servers = db.fetch_servers(&server_ids).await?;
|
||||||
|
|
||||||
@@ -151,6 +152,17 @@ impl State {
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
// Fetch customisations.
|
||||||
|
let emojis = Some(
|
||||||
|
db.fetch_emoji_by_parent_ids(
|
||||||
|
&servers
|
||||||
|
.iter()
|
||||||
|
.map(|x| x.id.to_string())
|
||||||
|
.collect::<Vec<String>>(),
|
||||||
|
)
|
||||||
|
.await?,
|
||||||
|
);
|
||||||
|
|
||||||
// Copy data into local state cache.
|
// Copy data into local state cache.
|
||||||
self.cache.users = users.iter().cloned().map(|x| (x.id.clone(), x)).collect();
|
self.cache.users = users.iter().cloned().map(|x| (x.id.clone(), x)).collect();
|
||||||
self.cache
|
self.cache
|
||||||
@@ -202,6 +214,7 @@ impl State {
|
|||||||
servers,
|
servers,
|
||||||
channels,
|
channels,
|
||||||
members,
|
members,
|
||||||
|
emojis,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -472,27 +485,23 @@ impl State {
|
|||||||
|
|
||||||
if data.rank.is_some() || data.permissions.is_some() {
|
if data.rank.is_some() || data.permissions.is_some() {
|
||||||
if let Some(member) = self.cache.members.get(id) {
|
if let Some(member) = self.cache.members.get(id) {
|
||||||
if let Some(roles) = &member.roles {
|
if member.roles.contains(role_id) {
|
||||||
if roles.contains(role_id) {
|
|
||||||
queue_server = Some(id.clone());
|
queue_server = Some(id.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
EventV1::ServerRoleDelete { id, role_id } => {
|
EventV1::ServerRoleDelete { id, role_id } => {
|
||||||
if let Some(server) = self.cache.servers.get_mut(id) {
|
if let Some(server) = self.cache.servers.get_mut(id) {
|
||||||
server.roles.remove(role_id);
|
server.roles.remove(role_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(member) = self.cache.members.get(id) {
|
if let Some(member) = self.cache.members.get(id) {
|
||||||
if let Some(roles) = &member.roles {
|
if member.roles.contains(role_id) {
|
||||||
if roles.contains(role_id) {
|
|
||||||
queue_server = Some(id.clone());
|
queue_server = Some(id.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
EventV1::UserRelationship { id, user, .. } => {
|
EventV1::UserRelationship { id, user, .. } => {
|
||||||
self.cache.users.insert(id.clone(), user.clone());
|
self.cache.users.insert(id.clone(), user.clone());
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
use std::collections::HashSet;
|
|
||||||
|
|
||||||
use crate::models::channel::{Channel, FieldsChannel, PartialChannel};
|
use crate::models::channel::{Channel, FieldsChannel, PartialChannel};
|
||||||
use crate::{AbstractAttachment, AbstractChannel, Error, OverrideField, Result};
|
use crate::{AbstractAttachment, AbstractChannel, Error, OverrideField, Result};
|
||||||
|
|
||||||
@@ -83,8 +81,4 @@ impl AbstractChannel for DummyDb {
|
|||||||
info!("Updating permissions for role {role} in {channel} with {permissions:?}");
|
info!("Updating permissions for role {role} in {channel} with {permissions:?}");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn check_channels_exist(&self, _channels: &HashSet<String>) -> Result<bool> {
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -64,4 +64,22 @@ impl AbstractMessage for DummyDb {
|
|||||||
) -> Result<Vec<Message>> {
|
) -> Result<Vec<Message>> {
|
||||||
Ok(vec![self.fetch_message(channel).await.unwrap()])
|
Ok(vec![self.fetch_message(channel).await.unwrap()])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add a new reaction to a message
|
||||||
|
async fn add_reaction(&self, id: &str, emoji: &str, user: &str) -> Result<()> {
|
||||||
|
info!("Add to {id} with {emoji} and {user}");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove a reaction from a message
|
||||||
|
async fn remove_reaction(&self, id: &str, emoji: &str, user: &str) -> Result<()> {
|
||||||
|
info!("Remove {emoji} from {id} for {user}");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove reaction from a message
|
||||||
|
async fn clear_reaction(&self, id: &str, emoji: &str) -> Result<()> {
|
||||||
|
info!("Clear {emoji} on {id}");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+5
@@ -39,4 +39,9 @@ impl AbstractAttachment for DummyDb {
|
|||||||
info!("Marked {id} as deleted");
|
info!("Marked {id} as deleted");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn mark_attachments_as_deleted(&self, ids: &[String]) -> Result<()> {
|
||||||
|
info!("Marked {ids:?} as deleted");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
use crate::models::emoji::EmojiParent;
|
||||||
|
use crate::models::Emoji;
|
||||||
|
use crate::{AbstractEmoji, Result};
|
||||||
|
|
||||||
|
use super::super::DummyDb;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl AbstractEmoji for DummyDb {
|
||||||
|
/// Fetch an emoji by its id
|
||||||
|
async fn fetch_emoji(&self, id: &str) -> Result<Emoji> {
|
||||||
|
Ok(Emoji {
|
||||||
|
id: id.into(),
|
||||||
|
name: id.into(),
|
||||||
|
parent: EmojiParent::Server { id: id.into() },
|
||||||
|
creator_id: id.into(),
|
||||||
|
animated: false,
|
||||||
|
nsfw: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fetch emoji by their ids
|
||||||
|
async fn fetch_emoji_by_parent_id(&self, parent_id: &str) -> Result<Vec<Emoji>> {
|
||||||
|
Ok(vec![self.fetch_emoji(parent_id).await?])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fetch emoji by their parent ids
|
||||||
|
async fn fetch_emoji_by_parent_ids(&self, _parent_ids: &[String]) -> Result<Vec<Emoji>> {
|
||||||
|
Ok(vec![])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Insert emoji into database.
|
||||||
|
async fn insert_emoji(&self, emoji: &Emoji) -> Result<()> {
|
||||||
|
info!("Insert {emoji:?}");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Detach an emoji by its id
|
||||||
|
async fn detach_emoji(&self, emoji: &Emoji) -> Result<()> {
|
||||||
|
info!("Detach {emoji:?}");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,8 +4,9 @@ pub mod admin {
|
|||||||
pub mod migrations;
|
pub mod migrations;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod autumn {
|
pub mod media {
|
||||||
pub mod attachment;
|
pub mod attachment;
|
||||||
|
pub mod emoji;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod channels {
|
pub mod channels {
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ use crate::{AbstractServerMember, Result};
|
|||||||
|
|
||||||
use super::super::DummyDb;
|
use super::super::DummyDb;
|
||||||
|
|
||||||
|
use iso8601_timestamp::Timestamp;
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl AbstractServerMember for DummyDb {
|
impl AbstractServerMember for DummyDb {
|
||||||
async fn fetch_member(&self, server: &str, user: &str) -> Result<Member> {
|
async fn fetch_member(&self, server: &str, user: &str) -> Result<Member> {
|
||||||
@@ -11,9 +13,11 @@ impl AbstractServerMember for DummyDb {
|
|||||||
server: server.into(),
|
server: server.into(),
|
||||||
user: user.into(),
|
user: user.into(),
|
||||||
},
|
},
|
||||||
|
joined_at: Timestamp::now_utc(),
|
||||||
nickname: None,
|
nickname: None,
|
||||||
avatar: None,
|
avatar: None,
|
||||||
roles: None,
|
roles: vec![],
|
||||||
|
timeout: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -242,7 +242,12 @@ impl Channel {
|
|||||||
/// Add user to a group
|
/// Add user to a group
|
||||||
pub async fn add_user_to_group(&mut self, db: &Database, user: &str, by: &str) -> Result<()> {
|
pub async fn add_user_to_group(&mut self, db: &Database, user: &str, by: &str) -> Result<()> {
|
||||||
if let Channel::Group { recipients, .. } = self {
|
if let Channel::Group { recipients, .. } = self {
|
||||||
recipients.push(user.to_string());
|
let user = user.to_string();
|
||||||
|
if recipients.contains(&user) {
|
||||||
|
return Err(Error::AlreadyInGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
recipients.push(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
match &self {
|
match &self {
|
||||||
@@ -281,6 +286,7 @@ impl Channel {
|
|||||||
db: &Database,
|
db: &Database,
|
||||||
user: &str,
|
user: &str,
|
||||||
by: Option<&str>,
|
by: Option<&str>,
|
||||||
|
silent: bool,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
match &self {
|
match &self {
|
||||||
Channel::Group {
|
Channel::Group {
|
||||||
@@ -300,6 +306,15 @@ impl Channel {
|
|||||||
vec![],
|
vec![],
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
SystemMessage::ChannelOwnershipChanged {
|
||||||
|
from: owner.to_string(),
|
||||||
|
to: new_owner.into(),
|
||||||
|
}
|
||||||
|
.into_message(id.to_string())
|
||||||
|
.create(db, self, None)
|
||||||
|
.await
|
||||||
|
.ok();
|
||||||
} else {
|
} else {
|
||||||
db.delete_channel(self).await?;
|
db.delete_channel(self).await?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
@@ -315,6 +330,7 @@ impl Channel {
|
|||||||
.p(id.to_string())
|
.p(id.to_string())
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
if !silent {
|
||||||
if let Some(by) = by {
|
if let Some(by) = by {
|
||||||
SystemMessage::UserRemove {
|
SystemMessage::UserRemove {
|
||||||
id: user.to_string(),
|
id: user.to_string(),
|
||||||
@@ -329,6 +345,7 @@ impl Channel {
|
|||||||
.create(db, self, None)
|
.create(db, self, None)
|
||||||
.await
|
.await
|
||||||
.ok();
|
.ok();
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,22 +2,25 @@ use std::collections::HashSet;
|
|||||||
|
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use ulid::Ulid;
|
use ulid::Ulid;
|
||||||
|
use validator::Validate;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
events::client::EventV1,
|
events::client::EventV1,
|
||||||
models::{
|
models::{
|
||||||
message::{
|
message::{
|
||||||
AppendMessage, BulkMessageResponse, PartialMessage, SendableEmbed, SystemMessage,
|
AppendMessage, BulkMessageResponse, Interactions, PartialMessage, SendableEmbed,
|
||||||
|
SystemMessage,
|
||||||
},
|
},
|
||||||
Channel, Message, User,
|
Channel, Emoji, Message, User,
|
||||||
},
|
},
|
||||||
|
permissions::PermissionCalculator,
|
||||||
presence::presence_filter_online,
|
presence::presence_filter_online,
|
||||||
tasks::ack::AckEvent,
|
tasks::ack::AckEvent,
|
||||||
types::{
|
types::{
|
||||||
january::{Embed, Text},
|
january::{Embed, Text},
|
||||||
push::PushNotification,
|
push::PushNotification,
|
||||||
},
|
},
|
||||||
Database, Result,
|
Database, Error, Permission, Result,
|
||||||
};
|
};
|
||||||
|
|
||||||
impl Message {
|
impl Message {
|
||||||
@@ -134,7 +137,17 @@ impl Message {
|
|||||||
|
|
||||||
/// Delete a message
|
/// Delete a message
|
||||||
pub async fn delete(self, db: &Database) -> Result<()> {
|
pub async fn delete(self, db: &Database) -> Result<()> {
|
||||||
|
let file_ids: Vec<String> = self
|
||||||
|
.attachments
|
||||||
|
.map(|files| files.iter().map(|file| file.id.to_string()).collect())
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
if !file_ids.is_empty() {
|
||||||
|
db.mark_attachments_as_deleted(&file_ids).await?;
|
||||||
|
}
|
||||||
|
|
||||||
db.delete_message(&self.id).await?;
|
db.delete_message(&self.id).await?;
|
||||||
|
|
||||||
EventV1::MessageDelete {
|
EventV1::MessageDelete {
|
||||||
id: self.id,
|
id: self.id,
|
||||||
channel: self.channel.clone(),
|
channel: self.channel.clone(),
|
||||||
@@ -155,6 +168,109 @@ impl Message {
|
|||||||
.await;
|
.await;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Validate the sum of content of a message is under threshold
|
||||||
|
pub fn validate_sum(
|
||||||
|
content: &Option<String>,
|
||||||
|
embeds: &Option<Vec<SendableEmbed>>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let mut running_total = 0;
|
||||||
|
if let Some(content) = content {
|
||||||
|
running_total += content.len();
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(embeds) = embeds {
|
||||||
|
for embed in embeds {
|
||||||
|
if let Some(desc) = &embed.description {
|
||||||
|
running_total += desc.len();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if running_total <= 2000 {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(Error::PayloadTooLarge)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a reaction to a message
|
||||||
|
pub async fn add_reaction(&self, db: &Database, user: &User, emoji: &str) -> Result<()> {
|
||||||
|
// Check how many reactions are already on the message
|
||||||
|
if self.reactions.len() >= 20 {
|
||||||
|
return Err(Error::InvalidOperation);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the emoji is whitelisted
|
||||||
|
if !self.interactions.can_use(emoji) {
|
||||||
|
return Err(Error::InvalidOperation);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the emoji is usable by us
|
||||||
|
if !Emoji::can_use(db, emoji).await? {
|
||||||
|
return Err(Error::InvalidOperation);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send reaction event
|
||||||
|
EventV1::MessageReact {
|
||||||
|
id: self.id.to_string(),
|
||||||
|
channel_id: self.channel.to_string(),
|
||||||
|
user_id: user.id.to_string(),
|
||||||
|
emoji_id: emoji.to_string(),
|
||||||
|
}
|
||||||
|
.p(self.channel.to_string())
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// Add emoji
|
||||||
|
db.add_reaction(&self.id, emoji, &user.id).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove a reaction from a message
|
||||||
|
pub async fn remove_reaction(&self, db: &Database, user: &str, emoji: &str) -> Result<()> {
|
||||||
|
// Check if it actually exists
|
||||||
|
let empty = if let Some(users) = self.reactions.get(emoji) {
|
||||||
|
if !users.contains(user) {
|
||||||
|
return Err(Error::NotFound);
|
||||||
|
}
|
||||||
|
|
||||||
|
users.len() == 1
|
||||||
|
} else {
|
||||||
|
return Err(Error::NotFound);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Send reaction event
|
||||||
|
EventV1::MessageUnreact {
|
||||||
|
id: self.id.to_string(),
|
||||||
|
channel_id: self.channel.to_string(),
|
||||||
|
user_id: user.to_string(),
|
||||||
|
emoji_id: emoji.to_string(),
|
||||||
|
}
|
||||||
|
.p(self.channel.to_string())
|
||||||
|
.await;
|
||||||
|
|
||||||
|
if empty {
|
||||||
|
// If empty, remove the reaction entirely
|
||||||
|
db.clear_reaction(&self.id, emoji).await
|
||||||
|
} else {
|
||||||
|
// Otherwise only remove that one reaction
|
||||||
|
db.remove_reaction(&self.id, emoji, user).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove a reaction from a message
|
||||||
|
pub async fn clear_reaction(&self, db: &Database, emoji: &str) -> Result<()> {
|
||||||
|
// Send reaction event
|
||||||
|
EventV1::MessageRemoveReaction {
|
||||||
|
id: self.id.to_string(),
|
||||||
|
channel_id: self.channel.to_string(),
|
||||||
|
emoji_id: emoji.to_string(),
|
||||||
|
}
|
||||||
|
.p(self.channel.to_string())
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// Write to database
|
||||||
|
db.clear_reaction(&self.id, emoji).await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait IntoUsers {
|
pub trait IntoUsers {
|
||||||
@@ -226,12 +342,18 @@ impl From<SystemMessage> for String {
|
|||||||
"Channel description changed.".to_string()
|
"Channel description changed.".to_string()
|
||||||
}
|
}
|
||||||
SystemMessage::ChannelIconChanged { .. } => "Channel icon changed.".to_string(),
|
SystemMessage::ChannelIconChanged { .. } => "Channel icon changed.".to_string(),
|
||||||
|
SystemMessage::ChannelOwnershipChanged { .. } => {
|
||||||
|
"Channel ownership changed.".to_string()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SendableEmbed {
|
impl SendableEmbed {
|
||||||
pub async fn into_embed(self, db: &Database, message_id: String) -> Result<Embed> {
|
pub async fn into_embed(self, db: &Database, message_id: String) -> Result<Embed> {
|
||||||
|
self.validate()
|
||||||
|
.map_err(|error| Error::FailedValidation { error })?;
|
||||||
|
|
||||||
let media = if let Some(id) = self.media {
|
let media = if let Some(id) = self.media {
|
||||||
Some(
|
Some(
|
||||||
db.find_and_use_attachment(&id, "attachments", "message", &message_id)
|
db.find_and_use_attachment(&id, "attachments", "message", &message_id)
|
||||||
@@ -282,3 +404,46 @@ impl BulkMessageResponse {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Interactions {
|
||||||
|
/// Validate interactions info is correct
|
||||||
|
pub async fn validate(
|
||||||
|
&self,
|
||||||
|
db: &Database,
|
||||||
|
permissions: &mut PermissionCalculator<'_>,
|
||||||
|
) -> Result<()> {
|
||||||
|
if let Some(reactions) = &self.reactions {
|
||||||
|
permissions.throw_permission(db, Permission::React).await?;
|
||||||
|
|
||||||
|
if reactions.len() > 20 {
|
||||||
|
return Err(Error::InvalidOperation);
|
||||||
|
}
|
||||||
|
|
||||||
|
for reaction in reactions {
|
||||||
|
if !Emoji::can_use(db, reaction).await? {
|
||||||
|
return Err(Error::InvalidOperation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if we can use a given emoji to react
|
||||||
|
pub fn can_use(&self, emoji: &str) -> bool {
|
||||||
|
if self.restrict_reactions {
|
||||||
|
if let Some(reactions) = &self.reactions {
|
||||||
|
reactions.contains(emoji)
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if default initialisation of fields
|
||||||
|
pub fn is_default(&self) -> bool {
|
||||||
|
!self.restrict_reactions && self.reactions.is_none()
|
||||||
|
}
|
||||||
|
}
|
||||||
+5
@@ -30,4 +30,9 @@ impl File {
|
|||||||
db.find_and_use_attachment(id, "banners", "server", parent)
|
db.find_and_use_attachment(id, "banners", "server", parent)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn use_emoji(db: &Database, id: &str, parent: &str) -> Result<File> {
|
||||||
|
db.find_and_use_attachment(id, "emojis", "object", parent)
|
||||||
|
.await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
use std::{collections::HashSet, str::FromStr};
|
||||||
|
|
||||||
|
use ulid::Ulid;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
events::client::EventV1,
|
||||||
|
models::{emoji::EmojiParent, Emoji},
|
||||||
|
Database, Result,
|
||||||
|
};
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
/// Permissible emojis
|
||||||
|
static ref PERMISSIBLE_EMOJIS: HashSet<String> = include_str!(crate::asset!("emojis.txt"))
|
||||||
|
.split('\n')
|
||||||
|
.map(|x| x.into())
|
||||||
|
.collect();
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Emoji {
|
||||||
|
/// Get parent id
|
||||||
|
fn parent(&self) -> &str {
|
||||||
|
match &self.parent {
|
||||||
|
EmojiParent::Server { id } => id,
|
||||||
|
EmojiParent::Detached => "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create an emoji
|
||||||
|
pub async fn create(&self, db: &Database) -> Result<()> {
|
||||||
|
db.insert_emoji(self).await?;
|
||||||
|
EventV1::EmojiCreate(self.clone())
|
||||||
|
.p(self.parent().to_string())
|
||||||
|
.await;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete an emoji
|
||||||
|
pub async fn delete(self, db: &Database) -> Result<()> {
|
||||||
|
EventV1::EmojiDelete {
|
||||||
|
id: self.id.to_string(),
|
||||||
|
}
|
||||||
|
.p(self.parent().to_string())
|
||||||
|
.await;
|
||||||
|
|
||||||
|
db.detach_emoji(&self).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check whether we can use a given emoji
|
||||||
|
pub async fn can_use(db: &Database, emoji: &str) -> Result<bool> {
|
||||||
|
if Ulid::from_str(emoji).is_ok() {
|
||||||
|
db.fetch_emoji(emoji).await?;
|
||||||
|
Ok(true)
|
||||||
|
} else {
|
||||||
|
Ok(PERMISSIBLE_EMOJIS.contains(emoji))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,8 +4,9 @@ pub mod admin {
|
|||||||
pub mod migrations;
|
pub mod migrations;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod autumn {
|
pub mod media {
|
||||||
pub mod attachment;
|
pub mod attachment;
|
||||||
|
pub mod emoji;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod channels {
|
pub mod channels {
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use iso8601_timestamp::Timestamp;
|
||||||
use ulid::Ulid;
|
use ulid::Ulid;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -188,7 +191,11 @@ impl Server {
|
|||||||
server: self.id.clone(),
|
server: self.id.clone(),
|
||||||
user: user.id.clone(),
|
user: user.id.clone(),
|
||||||
},
|
},
|
||||||
..Default::default()
|
joined_at: Timestamp::now_utc(),
|
||||||
|
nickname: None,
|
||||||
|
avatar: None,
|
||||||
|
roles: vec![],
|
||||||
|
timeout: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
db.insert_member(&member).await?;
|
db.insert_member(&member).await?;
|
||||||
@@ -249,6 +256,7 @@ impl Server {
|
|||||||
db: &Database,
|
db: &Database,
|
||||||
member: Member,
|
member: Member,
|
||||||
intention: RemovalIntention,
|
intention: RemovalIntention,
|
||||||
|
silent: bool,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
db.delete_member(&member.id).await?;
|
db.delete_member(&member.id).await?;
|
||||||
|
|
||||||
@@ -259,6 +267,7 @@ impl Server {
|
|||||||
.p(member.id.server)
|
.p(member.id.server)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
if !silent {
|
||||||
if let Some(id) = self.system_messages.as_ref().and_then(|x| match intention {
|
if let Some(id) = self.system_messages.as_ref().and_then(|x| match intention {
|
||||||
RemovalIntention::Leave => x.user_left.as_ref(),
|
RemovalIntention::Leave => x.user_left.as_ref(),
|
||||||
RemovalIntention::Kick => x.user_kicked.as_ref(),
|
RemovalIntention::Kick => x.user_kicked.as_ref(),
|
||||||
@@ -274,10 +283,23 @@ impl Server {
|
|||||||
.await
|
.await
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create ban
|
||||||
|
pub async fn ban_user(
|
||||||
|
self,
|
||||||
|
db: &Database,
|
||||||
|
id: MemberCompositeKey,
|
||||||
|
reason: Option<String>,
|
||||||
|
) -> Result<ServerBan> {
|
||||||
|
let ban = ServerBan { id, reason };
|
||||||
|
db.insert_ban(&ban).await?;
|
||||||
|
Ok(ban)
|
||||||
|
}
|
||||||
|
|
||||||
/// Ban a member from a server
|
/// Ban a member from a server
|
||||||
pub async fn ban_member(
|
pub async fn ban_member(
|
||||||
self,
|
self,
|
||||||
@@ -285,37 +307,31 @@ impl Server {
|
|||||||
member: Member,
|
member: Member,
|
||||||
reason: Option<String>,
|
reason: Option<String>,
|
||||||
) -> Result<ServerBan> {
|
) -> Result<ServerBan> {
|
||||||
let ban = ServerBan {
|
self.remove_member(db, member.clone(), RemovalIntention::Ban, false)
|
||||||
id: member.id.clone(),
|
|
||||||
reason,
|
|
||||||
};
|
|
||||||
|
|
||||||
self.remove_member(db, member, RemovalIntention::Ban)
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
db.insert_ban(&ban).await?;
|
self.ban_user(db, member.id, reason).await
|
||||||
Ok(ban)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SystemMessageChannels {
|
impl SystemMessageChannels {
|
||||||
pub fn into_channel_ids(self) -> Vec<String> {
|
pub fn into_channel_ids(self) -> HashSet<String> {
|
||||||
let mut ids = vec![];
|
let mut ids = HashSet::new();
|
||||||
|
|
||||||
if let Some(id) = self.user_joined {
|
if let Some(id) = self.user_joined {
|
||||||
ids.push(id);
|
ids.insert(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(id) = self.user_left {
|
if let Some(id) = self.user_left {
|
||||||
ids.push(id);
|
ids.insert(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(id) = self.user_kicked {
|
if let Some(id) = self.user_kicked {
|
||||||
ids.push(id);
|
ids.insert(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(id) = self.user_banned {
|
if let Some(id) = self.user_banned {
|
||||||
ids.push(id);
|
ids.insert(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
ids
|
ids
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
use iso8601_timestamp::Timestamp;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
events::client::EventV1,
|
events::client::EventV1,
|
||||||
models::{
|
models::{
|
||||||
@@ -36,9 +38,8 @@ impl Member {
|
|||||||
|
|
||||||
/// Get this user's current ranking
|
/// Get this user's current ranking
|
||||||
pub fn get_ranking(&self, server: &Server) -> i64 {
|
pub fn get_ranking(&self, server: &Server) -> i64 {
|
||||||
if let Some(roles) = &self.roles {
|
|
||||||
let mut value = i64::MAX;
|
let mut value = i64::MAX;
|
||||||
for role in roles {
|
for role in &self.roles {
|
||||||
if let Some(role) = server.roles.get(role) {
|
if let Some(role) = server.roles.get(role) {
|
||||||
if role.rank < value {
|
if role.rank < value {
|
||||||
value = role.rank;
|
value = role.rank;
|
||||||
@@ -47,8 +48,14 @@ impl Member {
|
|||||||
}
|
}
|
||||||
|
|
||||||
value
|
value
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check whether this member is in timeout
|
||||||
|
pub fn in_timeout(&self) -> bool {
|
||||||
|
if let Some(timeout) = self.timeout {
|
||||||
|
*timeout > *Timestamp::now_utc()
|
||||||
} else {
|
} else {
|
||||||
i64::MAX
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,7 +63,8 @@ impl Member {
|
|||||||
match field {
|
match field {
|
||||||
FieldsMember::Avatar => self.avatar = None,
|
FieldsMember::Avatar => self.avatar = None,
|
||||||
FieldsMember::Nickname => self.nickname = None,
|
FieldsMember::Nickname => self.nickname = None,
|
||||||
FieldsMember::Roles => self.roles = None,
|
FieldsMember::Roles => self.roles.clear(),
|
||||||
|
FieldsMember::Timeout => self.timeout = None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8,9 +8,6 @@ use crate::{perms, Database, Error, Result};
|
|||||||
|
|
||||||
use futures::try_join;
|
use futures::try_join;
|
||||||
use impl_ops::impl_op_ex_commutative;
|
use impl_ops::impl_op_ex_commutative;
|
||||||
use okapi::openapi3::{SecurityScheme, SecuritySchemeData};
|
|
||||||
use rocket_okapi::gen::OpenApiGenerator;
|
|
||||||
use rocket_okapi::request::{OpenApiFromRequest, RequestHeaderInput};
|
|
||||||
use std::ops;
|
use std::ops;
|
||||||
|
|
||||||
impl_op_ex_commutative!(+ |a: &i32, b: &Badges| -> i32 { *a | *b as i32 });
|
impl_op_ex_commutative!(+ |a: &i32, b: &Badges| -> i32 { *a | *b as i32 });
|
||||||
@@ -153,18 +150,51 @@ impl User {
|
|||||||
Ok(db.fetch_server_count(&self.id).await? <= 100)
|
Ok(db.fetch_server_count(&self.id).await? <= 100)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update a user's username
|
/// Sanitise and validate a username can be used
|
||||||
pub async fn update_username(&mut self, db: &Database, username: String) -> Result<()> {
|
pub async fn validate_username(db: &Database, username: String) -> Result<String> {
|
||||||
|
// Trim surrounding spaces
|
||||||
let username = username.trim().to_string();
|
let username = username.trim().to_string();
|
||||||
|
|
||||||
|
// Make sure username is still at least 3 characters
|
||||||
|
if username.len() < 2 {
|
||||||
|
return Err(Error::InvalidUsername);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy the username for validation
|
||||||
|
let username_lowercase = username.to_lowercase();
|
||||||
|
|
||||||
|
// Ensure the username itself isn't blocked
|
||||||
|
const BLOCKED_USERNAMES: &[&str] = &["admin", "revolt"];
|
||||||
|
|
||||||
|
for username in BLOCKED_USERNAMES {
|
||||||
|
if username_lowercase == *username {
|
||||||
|
return Err(Error::InvalidUsername);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure none of the following substrings show up in the username
|
||||||
|
const BLOCKED_SUBSTRINGS: &[&str] = &["```"];
|
||||||
|
|
||||||
|
for substr in BLOCKED_SUBSTRINGS {
|
||||||
|
if username_lowercase.contains(substr) {
|
||||||
|
return Err(Error::InvalidUsername);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure the username isn't taken
|
||||||
if db.is_username_taken(&username).await? {
|
if db.is_username_taken(&username).await? {
|
||||||
return Err(Error::UsernameTaken);
|
return Err(Error::UsernameTaken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(username)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update a user's username
|
||||||
|
pub async fn update_username(&mut self, db: &Database, username: String) -> Result<()> {
|
||||||
self.update(
|
self.update(
|
||||||
db,
|
db,
|
||||||
PartialUser {
|
PartialUser {
|
||||||
username: Some(username),
|
username: Some(User::validate_username(db, username).await?),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
vec![],
|
vec![],
|
||||||
@@ -359,72 +389,3 @@ impl User {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
use rauth::entities::Session;
|
|
||||||
use rocket::http::Status;
|
|
||||||
use rocket::request::{self, FromRequest, Outcome, Request};
|
|
||||||
|
|
||||||
#[rocket::async_trait]
|
|
||||||
impl<'r> FromRequest<'r> for User {
|
|
||||||
type Error = rauth::util::Error;
|
|
||||||
|
|
||||||
async fn from_request(request: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
|
|
||||||
let user: &Option<User> = request
|
|
||||||
.local_cache_async(async {
|
|
||||||
let db = request
|
|
||||||
.rocket()
|
|
||||||
.state::<Database>()
|
|
||||||
.expect("Database state not reachable!");
|
|
||||||
|
|
||||||
let header_bot_token = request
|
|
||||||
.headers()
|
|
||||||
.get("x-bot-token")
|
|
||||||
.next()
|
|
||||||
.map(|x| x.to_string());
|
|
||||||
|
|
||||||
if let Some(bot_token) = header_bot_token {
|
|
||||||
if let Ok(user) = User::from_token(db, &bot_token, UserHint::Bot).await {
|
|
||||||
return Some(user);
|
|
||||||
}
|
|
||||||
} else if let Outcome::Success(session) = request.guard::<Session>().await {
|
|
||||||
// This uses a guard so can't really easily be refactored into from_token at this stage.
|
|
||||||
if let Ok(user) = db.fetch_user(&session.user_id).await {
|
|
||||||
return Some(user);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
|
|
||||||
if let Some(user) = user {
|
|
||||||
Outcome::Success(user.clone())
|
|
||||||
} else {
|
|
||||||
Outcome::Failure((Status::Unauthorized, rauth::util::Error::InvalidSession))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'r> OpenApiFromRequest<'r> for User {
|
|
||||||
fn from_request_input(
|
|
||||||
_gen: &mut OpenApiGenerator,
|
|
||||||
_name: String,
|
|
||||||
_required: bool,
|
|
||||||
) -> rocket_okapi::Result<RequestHeaderInput> {
|
|
||||||
let mut requirements = schemars::Map::new();
|
|
||||||
requirements.insert("Api Key".to_owned(), vec![]);
|
|
||||||
|
|
||||||
Ok(RequestHeaderInput::Security(
|
|
||||||
"Api Key".to_owned(),
|
|
||||||
SecurityScheme {
|
|
||||||
data: SecuritySchemeData::ApiKey {
|
|
||||||
name: "x-session-token".to_owned(),
|
|
||||||
location: "header".to_owned(),
|
|
||||||
},
|
|
||||||
description: Some("Session Token".to_owned()),
|
|
||||||
extensions: schemars::Map::new(),
|
|
||||||
},
|
|
||||||
requirements,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +1,10 @@
|
|||||||
pub mod dummy;
|
mod dummy;
|
||||||
pub mod generic;
|
mod generic;
|
||||||
pub mod mongo;
|
mod mongo;
|
||||||
|
|
||||||
|
#[cfg(feature = "rocket_impl")]
|
||||||
|
mod rocket;
|
||||||
|
|
||||||
|
pub use self::generic::users::user_settings::UserSettingsImpl;
|
||||||
|
pub use dummy::DummyDb;
|
||||||
|
pub use mongo::MongoDb;
|
||||||
@@ -2,7 +2,6 @@ use crate::r#impl::mongo::MongoDb;
|
|||||||
|
|
||||||
use super::scripts::LATEST_REVISION;
|
use super::scripts::LATEST_REVISION;
|
||||||
|
|
||||||
use log::info;
|
|
||||||
use mongodb::bson::doc;
|
use mongodb::bson::doc;
|
||||||
use mongodb::options::CreateCollectionOptions;
|
use mongodb::options::CreateCollectionOptions;
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use bson::Bson;
|
use bson::{Bson, DateTime};
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use log::info;
|
|
||||||
use mongodb::{
|
use mongodb::{
|
||||||
bson::{doc, from_bson, from_document, to_document, Document},
|
bson::{doc, from_bson, from_document, to_document, Document},
|
||||||
options::FindOptions,
|
options::FindOptions,
|
||||||
@@ -17,7 +16,7 @@ struct MigrationInfo {
|
|||||||
revision: i32,
|
revision: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const LATEST_REVISION: i32 = 15;
|
pub const LATEST_REVISION: i32 = 18;
|
||||||
|
|
||||||
pub async fn migrate_database(db: &MongoDb) {
|
pub async fn migrate_database(db: &MongoDb) {
|
||||||
let migrations = db.col::<Document>("migrations");
|
let migrations = db.col::<Document>("migrations");
|
||||||
@@ -603,6 +602,65 @@ pub async fn run_migrations(db: &MongoDb, revision: i32) -> i32 {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if revision <= 15 {
|
||||||
|
info!("Running migration [revision 15 / 04-06-2022]: Migrate rAuth to latest version.");
|
||||||
|
|
||||||
|
let db = rauth::Database::MongoDb(rauth::database::MongoDb(db.db()));
|
||||||
|
db.run_migration(rauth::Migration::M2022_06_03EnsureUpToSpec)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
if revision <= 16 {
|
||||||
|
info!("Running migration [revision 16 / 07-07-2022]: Add `emojis` collection and rAuth migration.");
|
||||||
|
|
||||||
|
let rauth_db = rauth::Database::MongoDb(rauth::database::MongoDb(db.db()));
|
||||||
|
rauth_db
|
||||||
|
.run_migration(rauth::Migration::M2022_06_09AddIndexForDeletion)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
db.db()
|
||||||
|
.create_collection("emojis", None)
|
||||||
|
.await
|
||||||
|
.expect("Failed to create emojis collection.");
|
||||||
|
|
||||||
|
db.db()
|
||||||
|
.run_command(
|
||||||
|
doc! {
|
||||||
|
"createIndexes": "emojis",
|
||||||
|
"indexes": [
|
||||||
|
{
|
||||||
|
"key": {
|
||||||
|
"parent.id": 1_i32,
|
||||||
|
},
|
||||||
|
"name": "parent_id"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("Failed to create emoji parent index.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if revision <= 17 {
|
||||||
|
info!("Running migration [revision 17 / 15-07-2022]: Initialise `joined_at` property on server members.");
|
||||||
|
|
||||||
|
db.col::<Document>("server_members")
|
||||||
|
.update_many(
|
||||||
|
doc! {},
|
||||||
|
doc! {
|
||||||
|
"$set": {
|
||||||
|
"joined_at": DateTime::now().to_rfc3339_string()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("Failed to update server members.");
|
||||||
|
}
|
||||||
|
|
||||||
// Need to migrate fields on attachments, change `user_id`, `object_id`, etc to `parent`.
|
// Need to migrate fields on attachments, change `user_id`, `object_id`, etc to `parent`.
|
||||||
|
|
||||||
// Reminder to update LATEST_REVISION when adding new migrations.
|
// Reminder to update LATEST_REVISION when adding new migrations.
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
use std::collections::HashSet;
|
|
||||||
|
|
||||||
use bson::{Bson, Document};
|
use bson::{Bson, Document};
|
||||||
|
|
||||||
use crate::models::channel::{Channel, FieldsChannel, PartialChannel};
|
use crate::models::channel::{Channel, FieldsChannel, PartialChannel};
|
||||||
@@ -291,25 +289,6 @@ impl AbstractChannel for MongoDb {
|
|||||||
with: "channel",
|
with: "channel",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn check_channels_exist(&self, channels: &HashSet<String>) -> Result<bool> {
|
|
||||||
let count = channels.len() as u64;
|
|
||||||
self.col::<Document>(COL)
|
|
||||||
.count_documents(
|
|
||||||
doc! {
|
|
||||||
"_id": {
|
|
||||||
"$in": channels.iter().cloned().collect::<Vec<String>>()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.map(|x| x == count)
|
|
||||||
.map_err(|_| Error::DatabaseError {
|
|
||||||
operation: "count_documents",
|
|
||||||
with: "channel",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoDocumentPath for FieldsChannel {
|
impl IntoDocumentPath for FieldsChannel {
|
||||||
|
|||||||
@@ -37,30 +37,46 @@ impl AbstractChannelUnread for MongoDb {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn acknowledge_channels(&self, user: &str, channels: &[String]) -> Result<()> {
|
async fn acknowledge_channels(&self, user: &str, channels: &[String]) -> Result<()> {
|
||||||
|
let current_time = Ulid::new().to_string();
|
||||||
|
|
||||||
self.col::<Document>(COL)
|
self.col::<Document>(COL)
|
||||||
.update_one(
|
.delete_many(
|
||||||
doc! {
|
doc! {
|
||||||
"_id.channel": {
|
"_id.channel": {
|
||||||
"$in": channels
|
"$in": channels
|
||||||
},
|
},
|
||||||
"_id.user": user,
|
"_id.user": user
|
||||||
},
|
},
|
||||||
doc! {
|
None,
|
||||||
"$unset": {
|
|
||||||
"mentions": 1_i32
|
|
||||||
},
|
|
||||||
"$set": {
|
|
||||||
"last_id": Ulid::new().to_string()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
UpdateOptions::builder().upsert(true).build(),
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map(|_| ())
|
|
||||||
.map_err(|_| Error::DatabaseError {
|
.map_err(|_| Error::DatabaseError {
|
||||||
operation: "update",
|
operation: "delete_many",
|
||||||
with: "channel_unread",
|
with: "channel_unreads",
|
||||||
|
})?;
|
||||||
|
|
||||||
|
self.col::<Document>(COL)
|
||||||
|
.insert_many(
|
||||||
|
channels
|
||||||
|
.iter()
|
||||||
|
.map(|channel| {
|
||||||
|
doc! {
|
||||||
|
"_id": {
|
||||||
|
"channel": channel,
|
||||||
|
"user": user
|
||||||
|
},
|
||||||
|
"last_id": ¤t_time
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
.collect::<Vec<Document>>(),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|_| Error::DatabaseError {
|
||||||
|
operation: "update_many",
|
||||||
|
with: "channel_unreads",
|
||||||
|
})
|
||||||
|
.map(|_| ())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn add_mention_to_unread<'a>(
|
async fn add_mention_to_unread<'a>(
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ impl MongoDb {
|
|||||||
pub async fn delete_bulk_messages(&self, projection: Document) -> Result<()> {
|
pub async fn delete_bulk_messages(&self, projection: Document) -> Result<()> {
|
||||||
let mut for_attachments = projection.clone();
|
let mut for_attachments = projection.clone();
|
||||||
for_attachments.insert(
|
for_attachments.insert(
|
||||||
"attachment",
|
"attachments",
|
||||||
doc! {
|
doc! {
|
||||||
"$exists": 1_i32
|
"$exists": 1_i32
|
||||||
},
|
},
|
||||||
@@ -126,10 +126,7 @@ impl AbstractMessage for MongoDb {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn delete_message(&self, id: &str) -> Result<()> {
|
async fn delete_message(&self, id: &str) -> Result<()> {
|
||||||
self.delete_bulk_messages(doc! {
|
self.delete_one_by_id(COL, id).await.map(|_| ())
|
||||||
"_id": id
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn delete_messages(&self, channel: &str, ids: Vec<String>) -> Result<()> {
|
async fn delete_messages(&self, channel: &str, ids: Vec<String>) -> Result<()> {
|
||||||
@@ -282,4 +279,70 @@ impl AbstractMessage for MongoDb {
|
|||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add a new reaction to a message
|
||||||
|
async fn add_reaction(&self, id: &str, emoji: &str, user: &str) -> Result<()> {
|
||||||
|
self.col::<Document>(COL)
|
||||||
|
.update_one(
|
||||||
|
doc! {
|
||||||
|
"_id": id
|
||||||
|
},
|
||||||
|
doc! {
|
||||||
|
"$addToSet": {
|
||||||
|
format!("reactions.{emoji}"): user
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map(|_| ())
|
||||||
|
.map_err(|_| Error::DatabaseError {
|
||||||
|
operation: "update_one",
|
||||||
|
with: "message",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove a reaction from a message
|
||||||
|
async fn remove_reaction(&self, id: &str, emoji: &str, user: &str) -> Result<()> {
|
||||||
|
self.col::<Document>(COL)
|
||||||
|
.update_one(
|
||||||
|
doc! {
|
||||||
|
"_id": id
|
||||||
|
},
|
||||||
|
doc! {
|
||||||
|
"$pull": {
|
||||||
|
format!("reactions.{emoji}"): user
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map(|_| ())
|
||||||
|
.map_err(|_| Error::DatabaseError {
|
||||||
|
operation: "update_one",
|
||||||
|
with: "message",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove reaction from a message
|
||||||
|
async fn clear_reaction(&self, id: &str, emoji: &str) -> Result<()> {
|
||||||
|
self.col::<Document>(COL)
|
||||||
|
.update_one(
|
||||||
|
doc! {
|
||||||
|
"_id": id
|
||||||
|
},
|
||||||
|
doc! {
|
||||||
|
"$unset": {
|
||||||
|
format!("reactions.{emoji}"): 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map(|_| ())
|
||||||
|
.map_err(|_| Error::DatabaseError {
|
||||||
|
operation: "update_one",
|
||||||
|
with: "message",
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+23
@@ -122,4 +122,27 @@ impl AbstractAttachment for MongoDb {
|
|||||||
with: "attachment",
|
with: "attachment",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn mark_attachments_as_deleted(&self, ids: &[String]) -> Result<()> {
|
||||||
|
self.col::<Document>(COL)
|
||||||
|
.update_many(
|
||||||
|
doc! {
|
||||||
|
"_id": {
|
||||||
|
"$in": ids
|
||||||
|
}
|
||||||
|
},
|
||||||
|
doc! {
|
||||||
|
"$set": {
|
||||||
|
"deleted": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map(|_| ())
|
||||||
|
.map_err(|_| Error::DatabaseError {
|
||||||
|
operation: "update",
|
||||||
|
with: "attachments",
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
use bson::Document;
|
||||||
|
|
||||||
|
use crate::models::Emoji;
|
||||||
|
use crate::{AbstractEmoji, Error, Result};
|
||||||
|
|
||||||
|
use super::super::MongoDb;
|
||||||
|
|
||||||
|
static COL: &str = "emojis";
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl AbstractEmoji for MongoDb {
|
||||||
|
/// Fetch an emoji by its id
|
||||||
|
async fn fetch_emoji(&self, id: &str) -> Result<Emoji> {
|
||||||
|
self.find_one_by_id(COL, id).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fetch emoji by their ids
|
||||||
|
async fn fetch_emoji_by_parent_id(&self, parent_id: &str) -> Result<Vec<Emoji>> {
|
||||||
|
self.find(
|
||||||
|
COL,
|
||||||
|
doc! {
|
||||||
|
"parent.id": parent_id
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fetch emoji by their parent ids
|
||||||
|
async fn fetch_emoji_by_parent_ids(&self, parent_ids: &[String]) -> Result<Vec<Emoji>> {
|
||||||
|
self.find(
|
||||||
|
COL,
|
||||||
|
doc! {
|
||||||
|
"parent.id": {
|
||||||
|
"$in": parent_ids
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Insert emoji into database.
|
||||||
|
async fn insert_emoji(&self, emoji: &Emoji) -> Result<()> {
|
||||||
|
self.insert_one(COL, emoji).await.map(|_| ())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete an emoji by its id
|
||||||
|
async fn detach_emoji(&self, emoji: &Emoji) -> Result<()> {
|
||||||
|
self.col::<Document>(COL)
|
||||||
|
.update_one(
|
||||||
|
doc! {
|
||||||
|
"_id": &emoji.id
|
||||||
|
},
|
||||||
|
doc! {
|
||||||
|
"$set": {
|
||||||
|
"parent": {
|
||||||
|
"type": "Detached"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map(|_| ())
|
||||||
|
.map_err(|_| Error::DatabaseError {
|
||||||
|
operation: "update_one",
|
||||||
|
with: "emojis",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,8 +15,9 @@ pub mod admin {
|
|||||||
pub mod migrations;
|
pub mod migrations;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod autumn {
|
pub mod media {
|
||||||
pub mod attachment;
|
pub mod attachment;
|
||||||
|
pub mod emoji;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod channels {
|
pub mod channels {
|
||||||
|
|||||||
@@ -18,6 +18,20 @@ impl MongoDb {
|
|||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
// Delete all emoji.
|
||||||
|
self.col::<Document>("emojis")
|
||||||
|
.delete_many(
|
||||||
|
doc! {
|
||||||
|
"parent.id": &server.id
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|_| Error::DatabaseError {
|
||||||
|
operation: "delete_many",
|
||||||
|
with: "emojis",
|
||||||
|
})?;
|
||||||
|
|
||||||
// Delete all channels.
|
// Delete all channels.
|
||||||
self.col::<Document>("channels")
|
self.col::<Document>("channels")
|
||||||
.delete_many(
|
.delete_many(
|
||||||
|
|||||||
@@ -129,6 +129,7 @@ impl IntoDocumentPath for FieldsMember {
|
|||||||
FieldsMember::Avatar => "avatar",
|
FieldsMember::Avatar => "avatar",
|
||||||
FieldsMember::Nickname => "nickname",
|
FieldsMember::Nickname => "nickname",
|
||||||
FieldsMember::Roles => "roles",
|
FieldsMember::Roles => "roles",
|
||||||
|
FieldsMember::Timeout => "timeout",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
use okapi::openapi3::{SecurityScheme, SecuritySchemeData};
|
||||||
|
use rauth::models::Session;
|
||||||
|
use rocket_okapi::gen::OpenApiGenerator;
|
||||||
|
use rocket_okapi::request::{OpenApiFromRequest, RequestHeaderInput};
|
||||||
|
|
||||||
|
use rocket::http::Status;
|
||||||
|
use rocket::request::{self, FromRequest, Outcome, Request};
|
||||||
|
|
||||||
|
use crate::models::user::UserHint;
|
||||||
|
use crate::models::User;
|
||||||
|
use crate::Database;
|
||||||
|
|
||||||
|
#[rocket::async_trait]
|
||||||
|
impl<'r> FromRequest<'r> for User {
|
||||||
|
type Error = rauth::Error;
|
||||||
|
|
||||||
|
async fn from_request(request: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
|
||||||
|
let user: &Option<User> = request
|
||||||
|
.local_cache_async(async {
|
||||||
|
let db = request.rocket().state::<Database>().expect("`Database`");
|
||||||
|
|
||||||
|
let header_bot_token = request
|
||||||
|
.headers()
|
||||||
|
.get("x-bot-token")
|
||||||
|
.next()
|
||||||
|
.map(|x| x.to_string());
|
||||||
|
|
||||||
|
if let Some(bot_token) = header_bot_token {
|
||||||
|
if let Ok(user) = User::from_token(db, &bot_token, UserHint::Bot).await {
|
||||||
|
return Some(user);
|
||||||
|
}
|
||||||
|
} else if let Outcome::Success(session) = request.guard::<Session>().await {
|
||||||
|
// This uses a guard so can't really easily be refactored into from_token at this stage.
|
||||||
|
if let Ok(user) = db.fetch_user(&session.user_id).await {
|
||||||
|
return Some(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
if let Some(user) = user {
|
||||||
|
Outcome::Success(user.clone())
|
||||||
|
} else {
|
||||||
|
Outcome::Failure((Status::Unauthorized, rauth::Error::InvalidSession))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'r> OpenApiFromRequest<'r> for User {
|
||||||
|
fn from_request_input(
|
||||||
|
_gen: &mut OpenApiGenerator,
|
||||||
|
_name: String,
|
||||||
|
_required: bool,
|
||||||
|
) -> rocket_okapi::Result<RequestHeaderInput> {
|
||||||
|
let mut requirements = schemars::Map::new();
|
||||||
|
requirements.insert("Session Token".to_owned(), vec![]);
|
||||||
|
|
||||||
|
Ok(RequestHeaderInput::Security(
|
||||||
|
"Session Token".to_owned(),
|
||||||
|
SecurityScheme {
|
||||||
|
data: SecuritySchemeData::ApiKey {
|
||||||
|
name: "x-session-token".to_owned(),
|
||||||
|
location: "header".to_owned(),
|
||||||
|
},
|
||||||
|
description: Some("Used to authenticate as a user.".to_owned()),
|
||||||
|
extensions: schemars::Map::new(),
|
||||||
|
},
|
||||||
|
requirements,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
+19
-33
@@ -15,9 +15,10 @@ extern crate lazy_static;
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate bitfield;
|
extern crate bitfield;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
pub extern crate bson;
|
extern crate bson;
|
||||||
|
|
||||||
pub use iso8601_timestamp::Timestamp;
|
pub use iso8601_timestamp::Timestamp;
|
||||||
|
pub use rauth;
|
||||||
pub use redis_kiss;
|
pub use redis_kiss;
|
||||||
|
|
||||||
pub mod events;
|
pub mod events;
|
||||||
@@ -26,11 +27,14 @@ pub mod models;
|
|||||||
pub mod presence;
|
pub mod presence;
|
||||||
pub mod tasks;
|
pub mod tasks;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
|
pub mod util;
|
||||||
|
|
||||||
|
#[cfg(feature = "rocket_impl")]
|
||||||
|
pub mod web;
|
||||||
|
|
||||||
mod database;
|
mod database;
|
||||||
mod permissions;
|
mod permissions;
|
||||||
mod traits;
|
mod traits;
|
||||||
mod util;
|
|
||||||
|
|
||||||
pub use database::*;
|
pub use database::*;
|
||||||
pub use traits::*;
|
pub use traits::*;
|
||||||
@@ -38,38 +42,20 @@ pub use traits::*;
|
|||||||
pub use permissions::defn::*;
|
pub use permissions::defn::*;
|
||||||
pub use permissions::{get_relationship, perms};
|
pub use permissions::{get_relationship, perms};
|
||||||
|
|
||||||
pub use util::r#ref::Ref;
|
pub use util::{
|
||||||
pub use util::result::{Error, Result};
|
r#ref::Ref,
|
||||||
pub use util::variables;
|
result::{Error, Result},
|
||||||
|
variables,
|
||||||
|
};
|
||||||
|
|
||||||
#[cfg(feature = "rocket_impl")]
|
#[cfg(feature = "rocket_impl")]
|
||||||
pub use rocket_empty::EmptyResponse;
|
pub use web::{Db, EmptyResponse};
|
||||||
|
|
||||||
#[cfg(feature = "rocket_impl")]
|
/// Resolve asset
|
||||||
use rocket::State;
|
macro_rules! asset {
|
||||||
|
($path:literal) => {
|
||||||
#[cfg(feature = "rocket_impl")]
|
concat!(env!("CARGO_MANIFEST_DIR"), "/assets/", $path)
|
||||||
pub type Db = State<Database>;
|
};
|
||||||
|
|
||||||
/// Configure logging and common Rust variables
|
|
||||||
pub fn setup_logging() -> sentry::ClientInitGuard {
|
|
||||||
dotenv::dotenv().ok();
|
|
||||||
|
|
||||||
if std::env::var("RUST_LOG").is_err() {
|
|
||||||
std::env::set_var("RUST_LOG", "info");
|
|
||||||
}
|
|
||||||
|
|
||||||
if std::env::var("ROCKET_ADDRESS").is_err() {
|
|
||||||
std::env::set_var("ROCKET_ADDRESS", "0.0.0.0");
|
|
||||||
}
|
|
||||||
|
|
||||||
pretty_env_logger::init();
|
|
||||||
|
|
||||||
sentry::init((
|
|
||||||
"https://62fd0e02c5354905b4e286757f4beb16@sentry.insert.moe/4",
|
|
||||||
sentry::ClientOptions {
|
|
||||||
release: sentry::release_name!(),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) use asset;
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
|
use crate::util::regex::RE_COLOUR;
|
||||||
|
|
||||||
|
use indexmap::{IndexMap, IndexSet};
|
||||||
|
use iso8601_timestamp::Timestamp;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use validator::Validate;
|
use validator::Validate;
|
||||||
|
|
||||||
use iso8601_timestamp::Timestamp;
|
|
||||||
|
|
||||||
#[cfg(feature = "rocket_impl")]
|
#[cfg(feature = "rocket_impl")]
|
||||||
use rocket::FromFormField;
|
use rocket::FromFormField;
|
||||||
|
|
||||||
@@ -11,6 +13,11 @@ use crate::{
|
|||||||
types::january::Embed,
|
types::january::Embed,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Utility function to check if a boolean value is false
|
||||||
|
pub fn if_false(t: &bool) -> bool {
|
||||||
|
!t
|
||||||
|
}
|
||||||
|
|
||||||
/// # Reply
|
/// # Reply
|
||||||
///
|
///
|
||||||
/// Representation of a message reply before it is sent.
|
/// Representation of a message reply before it is sent.
|
||||||
@@ -33,7 +40,7 @@ pub struct SendableEmbed {
|
|||||||
#[validate(length(min = 1, max = 2000))]
|
#[validate(length(min = 1, max = 2000))]
|
||||||
pub description: Option<String>,
|
pub description: Option<String>,
|
||||||
pub media: Option<String>,
|
pub media: Option<String>,
|
||||||
#[validate(length(min = 1, max = 64))]
|
#[validate(length(min = 1, max = 128), regex = "RE_COLOUR")]
|
||||||
pub colour: Option<String>,
|
pub colour: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,6 +68,8 @@ pub enum SystemMessage {
|
|||||||
ChannelDescriptionChanged { by: String },
|
ChannelDescriptionChanged { by: String },
|
||||||
#[serde(rename = "channel_icon_changed")]
|
#[serde(rename = "channel_icon_changed")]
|
||||||
ChannelIconChanged { by: String },
|
ChannelIconChanged { by: String },
|
||||||
|
#[serde(rename = "channel_ownership_changed")]
|
||||||
|
ChannelOwnershipChanged { from: String, to: String },
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Name and / or avatar override information
|
/// Name and / or avatar override information
|
||||||
@@ -74,6 +83,23 @@ pub struct Masquerade {
|
|||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
#[validate(length(min = 1, max = 128))]
|
#[validate(length(min = 1, max = 128))]
|
||||||
pub avatar: Option<String>,
|
pub avatar: Option<String>,
|
||||||
|
/// Replace the display role colour shown on this message
|
||||||
|
///
|
||||||
|
/// Must have `ManageRole` permission to use
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
#[validate(length(min = 1, max = 128), regex = "RE_COLOUR")]
|
||||||
|
pub colour: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Information to guide interactions on this message
|
||||||
|
#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone, Validate, Default)]
|
||||||
|
pub struct Interactions {
|
||||||
|
/// Reactions which should always appear and be distinct
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none", default)]
|
||||||
|
pub reactions: Option<IndexSet<String>>,
|
||||||
|
/// Whether reactions should be restricted to the given list
|
||||||
|
#[serde(skip_serializing_if = "if_false", default)]
|
||||||
|
pub restrict_reactions: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Representation of a Message on Revolt
|
/// Representation of a Message on Revolt
|
||||||
@@ -115,6 +141,12 @@ pub struct Message {
|
|||||||
/// Array of message ids this message is replying to
|
/// Array of message ids this message is replying to
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub replies: Option<Vec<String>>,
|
pub replies: Option<Vec<String>>,
|
||||||
|
/// Hashmap of emoji IDs to array of user IDs
|
||||||
|
#[serde(skip_serializing_if = "IndexMap::is_empty", default)]
|
||||||
|
pub reactions: IndexMap<String, IndexSet<String>>,
|
||||||
|
/// Information about how this message should be interacted with
|
||||||
|
#[serde(skip_serializing_if = "Interactions::is_default", default)]
|
||||||
|
pub interactions: Interactions,
|
||||||
/// Name and / or avatar overrides for this message
|
/// Name and / or avatar overrides for this message
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub masquerade: Option<Masquerade>,
|
pub masquerade: Option<Masquerade>,
|
||||||
|
|||||||
File renamed without changes.
@@ -0,0 +1,34 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
/// Utility function to check if a boolean value is false
|
||||||
|
pub fn if_false(t: &bool) -> bool {
|
||||||
|
!t
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Information about what owns this emoji
|
||||||
|
#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)]
|
||||||
|
#[serde(tag = "type")]
|
||||||
|
pub enum EmojiParent {
|
||||||
|
Server { id: String },
|
||||||
|
Detached,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Representation of an Emoji on Revolt
|
||||||
|
#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)]
|
||||||
|
pub struct Emoji {
|
||||||
|
/// Unique Id
|
||||||
|
#[serde(rename = "_id")]
|
||||||
|
pub id: String,
|
||||||
|
/// What owns this emoji
|
||||||
|
pub parent: EmojiParent,
|
||||||
|
/// Uploader user id
|
||||||
|
pub creator_id: String,
|
||||||
|
/// Emoji name
|
||||||
|
pub name: String,
|
||||||
|
/// Whether the emoji is animated
|
||||||
|
#[serde(skip_serializing_if = "if_false", default)]
|
||||||
|
pub animated: bool,
|
||||||
|
/// Whether the emoji is marked as nsfw
|
||||||
|
#[serde(skip_serializing_if = "if_false", default)]
|
||||||
|
pub nsfw: bool,
|
||||||
|
}
|
||||||
@@ -3,8 +3,9 @@ mod admin {
|
|||||||
pub mod simple;
|
pub mod simple;
|
||||||
}
|
}
|
||||||
|
|
||||||
mod autumn {
|
mod media {
|
||||||
pub mod attachment;
|
pub mod attachment;
|
||||||
|
pub mod emoji;
|
||||||
}
|
}
|
||||||
|
|
||||||
mod channels {
|
mod channels {
|
||||||
@@ -27,8 +28,8 @@ mod users {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub use admin::*;
|
pub use admin::*;
|
||||||
pub use autumn::*;
|
|
||||||
pub use channels::*;
|
pub use channels::*;
|
||||||
|
pub use media::*;
|
||||||
pub use servers::*;
|
pub use servers::*;
|
||||||
pub use users::*;
|
pub use users::*;
|
||||||
|
|
||||||
@@ -37,6 +38,7 @@ pub use bot::Bot;
|
|||||||
pub use channel::Channel;
|
pub use channel::Channel;
|
||||||
pub use channel_invite::Invite;
|
pub use channel_invite::Invite;
|
||||||
pub use channel_unread::ChannelUnread;
|
pub use channel_unread::ChannelUnread;
|
||||||
|
pub use emoji::Emoji;
|
||||||
pub use message::Message;
|
pub use message::Message;
|
||||||
pub use migrations::MigrationInfo;
|
pub use migrations::MigrationInfo;
|
||||||
pub use server::Server;
|
pub use server::Server;
|
||||||
|
|||||||
@@ -0,0 +1,205 @@
|
|||||||
|
<mxfile host="65bd71144e">
|
||||||
|
<diagram id="9BKyaxnbqnTkbfLuLd1z" name="Page-1">
|
||||||
|
<mxGraphModel dx="1033" dy="710" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
|
||||||
|
<root>
|
||||||
|
<mxCell id="0"/>
|
||||||
|
<mxCell id="1" parent="0"/>
|
||||||
|
<mxCell id="56" style="edgeStyle=orthogonalEdgeStyle;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;startArrow=none;startFill=0;endArrow=diamondThin;endFill=0;strokeColor=#B3B3B3;dashed=1;" parent="1" source="2" target="6" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="57" style="edgeStyle=orthogonalEdgeStyle;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.75;entryY=1;entryDx=0;entryDy=0;startArrow=none;startFill=0;endArrow=diamondThin;endFill=0;strokeColor=#B3B3B3;dashed=1;" parent="1" source="2" target="5" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry">
|
||||||
|
<Array as="points">
|
||||||
|
<mxPoint x="260" y="370"/>
|
||||||
|
<mxPoint x="275" y="370"/>
|
||||||
|
</Array>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="58" style="edgeStyle=orthogonalEdgeStyle;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.25;entryY=1;entryDx=0;entryDy=0;startArrow=none;startFill=0;endArrow=diamondThin;endFill=0;strokeColor=#B3B3B3;dashed=1;" parent="1" source="2" target="9" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry">
|
||||||
|
<Array as="points">
|
||||||
|
<mxPoint x="260" y="370"/>
|
||||||
|
<mxPoint x="342" y="370"/>
|
||||||
|
</Array>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="59" style="edgeStyle=orthogonalEdgeStyle;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;startArrow=none;startFill=0;endArrow=diamondThin;endFill=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;strokeColor=#B3B3B3;dashed=1;" parent="1" source="2" target="12" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry">
|
||||||
|
<mxPoint x="300" y="250" as="targetPoint"/>
|
||||||
|
<Array as="points">
|
||||||
|
<mxPoint x="260" y="370"/>
|
||||||
|
<mxPoint x="300" y="370"/>
|
||||||
|
</Array>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="2" value="Attachment" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="220" y="490" width="80" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="49" style="edgeStyle=orthogonalEdgeStyle;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.25;entryY=1;entryDx=0;entryDy=0;startArrow=none;startFill=0;endArrow=diamondThin;endFill=1;" parent="1" source="3" target="5" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry">
|
||||||
|
<Array as="points">
|
||||||
|
<mxPoint x="195" y="340"/>
|
||||||
|
<mxPoint x="245" y="340"/>
|
||||||
|
</Array>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="51" style="edgeStyle=orthogonalEdgeStyle;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;startArrow=none;startFill=0;endArrow=diamondThin;endFill=1;entryX=0.25;entryY=1;entryDx=0;entryDy=0;" parent="1" source="3" target="12" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry">
|
||||||
|
<mxPoint x="290" y="160" as="targetPoint"/>
|
||||||
|
<Array as="points">
|
||||||
|
<mxPoint x="195" y="260"/>
|
||||||
|
<mxPoint x="260" y="260"/>
|
||||||
|
<mxPoint x="260" y="220"/>
|
||||||
|
<mxPoint x="290" y="220"/>
|
||||||
|
</Array>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="3" value="Channel Invite" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="150" y="400" width="90" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="4" value="Channel Unread" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="40" y="400" width="100" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="43" style="edgeStyle=orthogonalEdgeStyle;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.25;entryY=1;entryDx=0;entryDy=0;startArrow=none;startFill=0;endArrow=diamondThin;endFill=1;dashed=1;" parent="1" source="5" target="12" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="60" style="edgeStyle=orthogonalEdgeStyle;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;dashed=1;startArrow=none;startFill=0;endArrow=diamondThin;endFill=1;strokeColor=#FFFFFF;" parent="1" source="5" target="9" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="5" value="Channel" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="230" y="280" width="60" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="45" style="edgeStyle=orthogonalEdgeStyle;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.75;entryY=1;entryDx=0;entryDy=0;startArrow=none;startFill=0;endArrow=diamondThin;endFill=1;" parent="1" source="6" target="12" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="46" style="edgeStyle=orthogonalEdgeStyle;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;startArrow=none;startFill=0;endArrow=diamondThin;endFill=1;" parent="1" source="6" target="5" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry">
|
||||||
|
<Array as="points">
|
||||||
|
<mxPoint x="310" y="355"/>
|
||||||
|
<mxPoint x="260" y="355"/>
|
||||||
|
</Array>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="6" value="Message" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="280" y="400" width="60" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="54" style="edgeStyle=orthogonalEdgeStyle;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;startArrow=none;startFill=0;endArrow=diamondThin;endFill=1;" parent="1" source="7" target="9" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry">
|
||||||
|
<Array as="points">
|
||||||
|
<mxPoint x="505" y="350"/>
|
||||||
|
<mxPoint x="355" y="350"/>
|
||||||
|
</Array>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="7" value="Server Ban" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="470" y="400" width="70" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="47" style="edgeStyle=orthogonalEdgeStyle;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;startArrow=none;startFill=0;endArrow=diamondThin;endFill=1;" parent="1" source="8" target="9" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry">
|
||||||
|
<Array as="points">
|
||||||
|
<mxPoint x="405" y="350"/>
|
||||||
|
<mxPoint x="355" y="350"/>
|
||||||
|
</Array>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="48" style="edgeStyle=orthogonalEdgeStyle;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.75;entryY=1;entryDx=0;entryDy=0;startArrow=none;startFill=0;endArrow=diamondThin;endFill=1;" parent="1" source="8" target="12" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry">
|
||||||
|
<Array as="points">
|
||||||
|
<mxPoint x="405" y="350"/>
|
||||||
|
<mxPoint x="310" y="350"/>
|
||||||
|
</Array>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="8" value="Server Member" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="360" y="400" width="90" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="29" style="edgeStyle=orthogonalEdgeStyle;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;endArrow=diamondThin;endFill=1;startArrow=diamondThin;startFill=1;" parent="1" source="10" target="12" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry">
|
||||||
|
<Array as="points">
|
||||||
|
<mxPoint x="240" y="145"/>
|
||||||
|
</Array>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="10" value="Bot" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="200" y="130" width="40" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="31" style="edgeStyle=orthogonalEdgeStyle;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.25;entryY=1;entryDx=0;entryDy=0;startArrow=none;startFill=0;endArrow=diamondThin;endFill=1;" parent="1" source="11" target="12" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="11" value="User Settings" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="150" y="210" width="90" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="15" style="edgeStyle=orthogonalEdgeStyle;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;endArrow=diamondThin;endFill=1;" parent="1" source="12" target="13" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="12" value="User" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="280" y="130" width="40" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="13" value="Account" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="310" y="40" width="60" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="17" style="edgeStyle=orthogonalEdgeStyle;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;endArrow=diamondThin;endFill=1;" parent="1" source="14" target="13" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="14" value="Session" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="350" y="130" width="60" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="44" style="edgeStyle=orthogonalEdgeStyle;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;startArrow=none;startFill=0;endArrow=diamondThin;endFill=1;entryX=0.75;entryY=1;entryDx=0;entryDy=0;" parent="1" source="9" target="12" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry">
|
||||||
|
<mxPoint x="310" y="160" as="targetPoint"/>
|
||||||
|
<Array as="points">
|
||||||
|
<mxPoint x="355" y="220"/>
|
||||||
|
<mxPoint x="310" y="220"/>
|
||||||
|
</Array>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="9" value="Server" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="330" y="280" width="50" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="55" style="edgeStyle=orthogonalEdgeStyle;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;startArrow=none;startFill=0;endArrow=diamondThin;endFill=1;" parent="1" source="4" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry">
|
||||||
|
<mxPoint x="290" y="160" as="targetPoint"/>
|
||||||
|
<mxPoint x="200" y="400" as="sourcePoint"/>
|
||||||
|
<Array as="points">
|
||||||
|
<mxPoint x="90" y="370"/>
|
||||||
|
<mxPoint x="195" y="370"/>
|
||||||
|
<mxPoint x="195" y="260"/>
|
||||||
|
<mxPoint x="260" y="260"/>
|
||||||
|
<mxPoint x="260" y="220"/>
|
||||||
|
<mxPoint x="290" y="220"/>
|
||||||
|
</Array>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="63" value="" style="edgeStyle=orthogonalEdgeStyle;html=1;startArrow=none;startFill=0;endArrow=diamondThin;endFill=0;strokeColor=#FFFFFF;entryX=1;entryY=1;entryDx=0;entryDy=0;" parent="1" source="61" target="12" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry">
|
||||||
|
<mxPoint x="584.25" y="335" as="targetPoint"/>
|
||||||
|
<Array as="points">
|
||||||
|
<mxPoint x="584" y="340"/>
|
||||||
|
<mxPoint x="390" y="340"/>
|
||||||
|
<mxPoint x="390" y="210"/>
|
||||||
|
<mxPoint x="320" y="210"/>
|
||||||
|
</Array>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="64" style="edgeStyle=orthogonalEdgeStyle;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.75;entryY=1;entryDx=0;entryDy=0;startArrow=none;startFill=0;endArrow=diamondThin;endFill=1;strokeColor=#FFFFFF;" parent="1" source="61" target="9" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry">
|
||||||
|
<Array as="points">
|
||||||
|
<mxPoint x="584" y="340"/>
|
||||||
|
<mxPoint x="368" y="340"/>
|
||||||
|
<mxPoint x="368" y="310"/>
|
||||||
|
</Array>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="61" value="Emoji" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="558.5" y="400" width="51.5" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="65" style="edgeStyle=orthogonalEdgeStyle;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;startArrow=none;startFill=0;endArrow=diamondThin;endFill=0;strokeColor=#B3B3B3;dashed=1;" parent="1" source="2" target="61" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry">
|
||||||
|
<mxPoint x="270" y="500" as="sourcePoint"/>
|
||||||
|
<mxPoint x="320" y="440" as="targetPoint"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
</root>
|
||||||
|
</mxGraphModel>
|
||||||
|
</diagram>
|
||||||
|
</mxfile>
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use iso8601_timestamp::Timestamp;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::models::attachment::File;
|
use crate::models::attachment::File;
|
||||||
@@ -12,7 +13,7 @@ pub struct MemberCompositeKey {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Representation of a member of a server on Revolt
|
/// Representation of a member of a server on Revolt
|
||||||
#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone, OptionalStruct, Default)]
|
#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone, OptionalStruct)]
|
||||||
#[optional_derive(Serialize, Deserialize, JsonSchema, Debug, Default, Clone)]
|
#[optional_derive(Serialize, Deserialize, JsonSchema, Debug, Default, Clone)]
|
||||||
#[optional_name = "PartialMember"]
|
#[optional_name = "PartialMember"]
|
||||||
#[opt_skip_serializing_none]
|
#[opt_skip_serializing_none]
|
||||||
@@ -22,6 +23,9 @@ pub struct Member {
|
|||||||
#[serde(rename = "_id")]
|
#[serde(rename = "_id")]
|
||||||
pub id: MemberCompositeKey,
|
pub id: MemberCompositeKey,
|
||||||
|
|
||||||
|
/// Time at which this user joined the server
|
||||||
|
pub joined_at: Timestamp,
|
||||||
|
|
||||||
/// Member's nickname
|
/// Member's nickname
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub nickname: Option<String>,
|
pub nickname: Option<String>,
|
||||||
@@ -30,8 +34,11 @@ pub struct Member {
|
|||||||
pub avatar: Option<File>,
|
pub avatar: Option<File>,
|
||||||
|
|
||||||
/// Member's roles
|
/// Member's roles
|
||||||
|
#[serde(skip_serializing_if = "Vec::is_empty", default)]
|
||||||
|
pub roles: Vec<String>,
|
||||||
|
/// Timestamp this member is timed out until
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub roles: Option<Vec<String>>,
|
pub timeout: Option<Timestamp>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Optional fields on server member object
|
/// Optional fields on server member object
|
||||||
@@ -40,6 +47,7 @@ pub enum FieldsMember {
|
|||||||
Nickname,
|
Nickname,
|
||||||
Avatar,
|
Avatar,
|
||||||
Roles,
|
Roles,
|
||||||
|
Timeout,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Member removal intention
|
/// Member removal intention
|
||||||
|
|||||||
@@ -45,8 +45,23 @@ impl Override {
|
|||||||
impl PermissionValue {
|
impl PermissionValue {
|
||||||
/// Apply a given override to this value
|
/// Apply a given override to this value
|
||||||
pub fn apply(&mut self, v: Override) {
|
pub fn apply(&mut self, v: Override) {
|
||||||
self.0 |= v.allow;
|
self.allow(v.allow);
|
||||||
self.0 &= !v.deny;
|
self.revoke(v.deny);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Allow given permissions
|
||||||
|
pub fn allow(&mut self, v: u64) {
|
||||||
|
self.0 |= v;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Revoke given permissions
|
||||||
|
pub fn revoke(&mut self, v: u64) {
|
||||||
|
self.0 &= !v;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Restrict to given permissions
|
||||||
|
pub fn restrict(&mut self, v: u64) {
|
||||||
|
self.0 &= v;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,8 +21,10 @@ pub enum Permission {
|
|||||||
ManagePermissions = 1 << 2,
|
ManagePermissions = 1 << 2,
|
||||||
/// Manage roles on server
|
/// Manage roles on server
|
||||||
ManageRole = 1 << 3,
|
ManageRole = 1 << 3,
|
||||||
|
/// Manage server customisation (includes emoji)
|
||||||
|
ManageCustomisation = 1 << 4,
|
||||||
|
|
||||||
// % 2 bits reserved
|
// % 1 bits reserved
|
||||||
|
|
||||||
// * Member permissions
|
// * Member permissions
|
||||||
/// Kick other members below their ranking
|
/// Kick other members below their ranking
|
||||||
@@ -63,8 +65,8 @@ pub enum Permission {
|
|||||||
UploadFiles = 1 << 27,
|
UploadFiles = 1 << 27,
|
||||||
/// Masquerade messages using custom nickname and avatar
|
/// Masquerade messages using custom nickname and avatar
|
||||||
Masquerade = 1 << 28,
|
Masquerade = 1 << 28,
|
||||||
|
/// React to messages with emojis
|
||||||
// % 1 bits reserved
|
React = 1 << 29,
|
||||||
|
|
||||||
// * Voice permissions
|
// * Voice permissions
|
||||||
/// Connect to a voice channel
|
/// Connect to a voice channel
|
||||||
@@ -96,6 +98,7 @@ impl_op_ex!(+ |a: &Permission, b: &Permission| -> u64 { *a as u64 | *b as u64 })
|
|||||||
impl_op_ex_commutative!(+ |a: &u64, b: &Permission| -> u64 { *a | *b as u64 });
|
impl_op_ex_commutative!(+ |a: &u64, b: &Permission| -> u64 { *a | *b as u64 });
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
|
pub static ref ALLOW_IN_TIMEOUT: u64 = Permission::ViewChannel + Permission::ReadMessageHistory;
|
||||||
pub static ref DEFAULT_PERMISSION_VIEW_ONLY: u64 =
|
pub static ref DEFAULT_PERMISSION_VIEW_ONLY: u64 =
|
||||||
Permission::ViewChannel + Permission::ReadMessageHistory;
|
Permission::ViewChannel + Permission::ReadMessageHistory;
|
||||||
pub static ref DEFAULT_PERMISSION: u64 = *DEFAULT_PERMISSION_VIEW_ONLY
|
pub static ref DEFAULT_PERMISSION: u64 = *DEFAULT_PERMISSION_VIEW_ONLY
|
||||||
@@ -122,6 +125,7 @@ bitfield! {
|
|||||||
pub can_manage_server, _: 62;
|
pub can_manage_server, _: 62;
|
||||||
pub can_manage_permissions, _: 61;
|
pub can_manage_permissions, _: 61;
|
||||||
pub can_manage_roles, _: 60;
|
pub can_manage_roles, _: 60;
|
||||||
|
pub can_manage_customisation, _: 59;
|
||||||
|
|
||||||
// * Member permissions
|
// * Member permissions
|
||||||
pub can_kick_members, _: 57;
|
pub can_kick_members, _: 57;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use std::collections::HashSet;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
models::Channel, permissions::PermissionCalculator, Override, Permission, PermissionValue,
|
models::Channel, permissions::PermissionCalculator, Override, Permission, PermissionValue,
|
||||||
Permissions, Perms, Result, DEFAULT_PERMISSION_DIRECT_MESSAGE,
|
Permissions, Perms, Result, ALLOW_IN_TIMEOUT, DEFAULT_PERMISSION_DIRECT_MESSAGE,
|
||||||
DEFAULT_PERMISSION_SAVED_MESSAGES, DEFAULT_PERMISSION_VIEW_ONLY,
|
DEFAULT_PERMISSION_SAVED_MESSAGES, DEFAULT_PERMISSION_VIEW_ONLY,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -55,11 +55,7 @@ async fn calculate_server_permission(
|
|||||||
let mut permissions: PermissionValue = server.default_permissions.into();
|
let mut permissions: PermissionValue = server.default_permissions.into();
|
||||||
|
|
||||||
// 4. Resolve each role in order.
|
// 4. Resolve each role in order.
|
||||||
let member_roles: HashSet<&String> = if let Some(roles) = member.roles.as_ref() {
|
let member_roles: HashSet<&String> = member.roles.iter().collect();
|
||||||
roles.iter().collect()
|
|
||||||
} else {
|
|
||||||
HashSet::new()
|
|
||||||
};
|
|
||||||
|
|
||||||
if !member_roles.is_empty() {
|
if !member_roles.is_empty() {
|
||||||
let mut roles = server
|
let mut roles = server
|
||||||
@@ -80,6 +76,11 @@ async fn calculate_server_permission(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 5. Revoke permissions if member is timed out.
|
||||||
|
if member.in_timeout() {
|
||||||
|
permissions.restrict(*ALLOW_IN_TIMEOUT);
|
||||||
|
}
|
||||||
|
|
||||||
Ok(permissions)
|
Ok(permissions)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,9 +111,17 @@ async fn calculate_channel_permission(
|
|||||||
|
|
||||||
// 1. Check channel type.
|
// 1. Check channel type.
|
||||||
let value: PermissionValue = match channel {
|
let value: PermissionValue = match channel {
|
||||||
Channel::SavedMessages { .. } => (*DEFAULT_PERMISSION_SAVED_MESSAGES).into(),
|
Channel::SavedMessages { user, .. } => {
|
||||||
|
if user == &data.perspective.id {
|
||||||
|
(*DEFAULT_PERMISSION_SAVED_MESSAGES).into()
|
||||||
|
} else {
|
||||||
|
0_u64.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
Channel::DirectMessage { recipients, .. } => {
|
Channel::DirectMessage { recipients, .. } => {
|
||||||
// 2. Fetch user.
|
// 2. Ensure we are a recipient.
|
||||||
|
if recipients.contains(&data.perspective.id) {
|
||||||
|
// 3. Fetch user.
|
||||||
let other_user = recipients
|
let other_user = recipients
|
||||||
.iter()
|
.iter()
|
||||||
.find(|x| x != &&data.perspective.id)
|
.find(|x| x != &&data.perspective.id)
|
||||||
@@ -121,15 +130,18 @@ async fn calculate_channel_permission(
|
|||||||
let user = db.fetch_user(other_user).await?;
|
let user = db.fetch_user(other_user).await?;
|
||||||
data.user.set(user);
|
data.user.set(user);
|
||||||
|
|
||||||
// 3. Calculate user permissions.
|
// 4. Calculate user permissions.
|
||||||
let perms = data.calc_user(db).await;
|
let perms = data.calc_user(db).await;
|
||||||
|
|
||||||
// 4. Check if the user can send messages.
|
// 5. Check if the user can send messages.
|
||||||
if perms.get_send_message() {
|
if perms.get_send_message() {
|
||||||
(*DEFAULT_PERMISSION_DIRECT_MESSAGE).into()
|
(*DEFAULT_PERMISSION_DIRECT_MESSAGE).into()
|
||||||
} else {
|
} else {
|
||||||
(*DEFAULT_PERMISSION_VIEW_ONLY).into()
|
(*DEFAULT_PERMISSION_VIEW_ONLY).into()
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
0_u64.into()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Channel::Group {
|
Channel::Group {
|
||||||
owner,
|
owner,
|
||||||
@@ -182,11 +194,7 @@ async fn calculate_channel_permission(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 4. Resolve each role in order.
|
// 4. Resolve each role in order.
|
||||||
let member_roles: HashSet<&String> = if let Some(roles) = member.roles.as_ref() {
|
let member_roles: HashSet<&String> = member.roles.iter().collect();
|
||||||
roles.iter().collect()
|
|
||||||
} else {
|
|
||||||
HashSet::new()
|
|
||||||
};
|
|
||||||
|
|
||||||
if !member_roles.is_empty() {
|
if !member_roles.is_empty() {
|
||||||
let mut roles = role_permissions
|
let mut roles = role_permissions
|
||||||
@@ -208,6 +216,11 @@ async fn calculate_channel_permission(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 5. Revoke permissions if member is timed out.
|
||||||
|
if member.in_timeout() {
|
||||||
|
permissions.restrict(*ALLOW_IN_TIMEOUT);
|
||||||
|
}
|
||||||
|
|
||||||
permissions
|
permissions
|
||||||
} else {
|
} else {
|
||||||
(Permission::GrantAllSafe as u64).into()
|
(Permission::GrantAllSafe as u64).into()
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
use crate::{models::channel::PartialChannel, Database};
|
use crate::{models::channel::PartialChannel, Database};
|
||||||
|
|
||||||
use deadqueue::limited::Queue;
|
use deadqueue::limited::Queue;
|
||||||
use log::info;
|
|
||||||
use mongodb::bson::doc;
|
use mongodb::bson::doc;
|
||||||
use std::{collections::HashMap, time::Duration};
|
use std::{collections::HashMap, time::Duration};
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ pub async fn start_workers(db: Database) {
|
|||||||
task::spawn(ack::worker(db.clone()));
|
task::spawn(ack::worker(db.clone()));
|
||||||
task::spawn(last_message_id::worker(db.clone()));
|
task::spawn(last_message_id::worker(db.clone()));
|
||||||
task::spawn(process_embeds::worker(db.clone()));
|
task::spawn(process_embeds::worker(db.clone()));
|
||||||
task::spawn(web_push::worker(db.clone()));
|
task::spawn(web_push::worker(db.clone().into()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loaded 100 of 122 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user