Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1b13eb9cc2 |
+1
-1
@@ -2,7 +2,7 @@
|
||||
node = "25.4.0"
|
||||
pnpm = "10.28.1"
|
||||
|
||||
gh = "2.25.0"
|
||||
gh = "2.95.0"
|
||||
|
||||
rust = "1.92.0"
|
||||
"cargo:cargo-nextest" = "0.9.122"
|
||||
|
||||
Generated
+79
-22
@@ -995,8 +995,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum-core",
|
||||
"axum-macros",
|
||||
"axum-core 0.4.5",
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"http 1.4.2",
|
||||
@@ -1005,7 +1004,7 @@ dependencies = [
|
||||
"hyper 1.10.1",
|
||||
"hyper-util",
|
||||
"itoa",
|
||||
"matchit",
|
||||
"matchit 0.7.3",
|
||||
"memchr",
|
||||
"mime",
|
||||
"multer",
|
||||
@@ -1024,6 +1023,41 @@ dependencies = [
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum"
|
||||
version = "0.8.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90"
|
||||
dependencies = [
|
||||
"axum-core 0.5.6",
|
||||
"axum-macros",
|
||||
"bytes",
|
||||
"form_urlencoded",
|
||||
"futures-util",
|
||||
"http 1.4.2",
|
||||
"http-body 1.0.1",
|
||||
"http-body-util",
|
||||
"hyper 1.10.1",
|
||||
"hyper-util",
|
||||
"itoa",
|
||||
"matchit 0.8.4",
|
||||
"memchr",
|
||||
"mime",
|
||||
"multer",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"serde_core",
|
||||
"serde_json",
|
||||
"serde_path_to_error",
|
||||
"serde_urlencoded",
|
||||
"sync_wrapper 1.0.2",
|
||||
"tokio",
|
||||
"tower",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum-core"
|
||||
version = "0.4.5"
|
||||
@@ -1046,34 +1080,51 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum-extra"
|
||||
version = "0.9.6"
|
||||
name = "axum-core"
|
||||
version = "0.5.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c794b30c904f0a1c2fb7740f7df7f7972dfaa14ef6f57cb6178dc63e5dca2f04"
|
||||
checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"axum-core",
|
||||
"bytes",
|
||||
"fastrand 2.4.1",
|
||||
"futures-core",
|
||||
"http 1.4.2",
|
||||
"http-body 1.0.1",
|
||||
"http-body-util",
|
||||
"mime",
|
||||
"pin-project-lite",
|
||||
"sync_wrapper 1.0.2",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum-extra"
|
||||
version = "0.12.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be44683b41ccb9ab2d23a5230015c9c3c55be97a25e4428366de8873103f7970"
|
||||
dependencies = [
|
||||
"axum 0.8.9",
|
||||
"axum-core 0.5.6",
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"headers",
|
||||
"http 1.4.2",
|
||||
"http-body 1.0.1",
|
||||
"http-body-util",
|
||||
"mime",
|
||||
"multer",
|
||||
"pin-project-lite",
|
||||
"serde",
|
||||
"tower",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum-macros"
|
||||
version = "0.4.2"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57d123550fa8d071b7255cb0cc04dc302baa6c8c4a79f55701552684d8399bce"
|
||||
checksum = "7aa268c23bfbbd2c4363b9cd302a4f504fb2a9dfe7e3451d66f35dd392e20aca"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote 1.0.46",
|
||||
@@ -1087,7 +1138,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05679dcdfeccf4cc6a8f45b80318fff8a9908337da1cfeb6acca00562a345b4d"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
"axum 0.7.9",
|
||||
"axum_typed_multipart_macros",
|
||||
"bytes",
|
||||
"chrono",
|
||||
@@ -4959,6 +5010,12 @@ version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94"
|
||||
|
||||
[[package]]
|
||||
name = "matchit"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
|
||||
|
||||
[[package]]
|
||||
name = "maybe-rayon"
|
||||
version = "0.1.1"
|
||||
@@ -7166,7 +7223,7 @@ dependencies = [
|
||||
name = "revolt-autumn"
|
||||
version = "0.13.7"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"axum 0.8.9",
|
||||
"axum-macros",
|
||||
"axum_typed_multipart",
|
||||
"ffprobe",
|
||||
@@ -7287,7 +7344,7 @@ dependencies = [
|
||||
"async-lock 2.8.0",
|
||||
"async-recursion",
|
||||
"async-trait",
|
||||
"axum",
|
||||
"axum 0.8.9",
|
||||
"base32",
|
||||
"base64 0.21.7",
|
||||
"bson",
|
||||
@@ -7417,7 +7474,7 @@ dependencies = [
|
||||
name = "revolt-gifbox"
|
||||
version = "0.13.7"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"axum 0.8.9",
|
||||
"axum-extra",
|
||||
"lru_time_cache",
|
||||
"reqwest 0.13.4",
|
||||
@@ -7441,7 +7498,7 @@ name = "revolt-january"
|
||||
version = "0.13.7"
|
||||
dependencies = [
|
||||
"async-recursion",
|
||||
"axum",
|
||||
"axum 0.8.9",
|
||||
"axum-extra",
|
||||
"encoding_rs",
|
||||
"lazy_static",
|
||||
@@ -7555,7 +7612,7 @@ name = "revolt-ratelimits"
|
||||
version = "0.13.7"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum",
|
||||
"axum 0.8.9",
|
||||
"dashmap",
|
||||
"log",
|
||||
"revolt-config",
|
||||
@@ -7570,7 +7627,7 @@ dependencies = [
|
||||
name = "revolt-result"
|
||||
version = "0.13.7"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"axum 0.8.9",
|
||||
"log",
|
||||
"revolt_okapi",
|
||||
"revolt_rocket_okapi",
|
||||
@@ -10072,7 +10129,7 @@ version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3ab4b7269d14d93626b0bfedf212f1b0995cb7d13d35daba21d579511e7fae8"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"axum 0.7.9",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"utoipa",
|
||||
|
||||
+3
-3
@@ -108,10 +108,10 @@ aws-config = "1.5.5"
|
||||
aws-sdk-s3 = "1.46.0"
|
||||
|
||||
# Axum (HTTP server)
|
||||
axum-macros = "0.4.1"
|
||||
axum-macros = "0.5.0"
|
||||
axum_typed_multipart = "0.12.1"
|
||||
axum = "0.7.5"
|
||||
axum-extra = "0.9"
|
||||
axum = "0.8.0"
|
||||
axum-extra = "0.12"
|
||||
tower-http = "0.5.2"
|
||||
|
||||
# Rocket (HTTP server)
|
||||
|
||||
@@ -88,10 +88,6 @@ max_concurrent_connections = 50
|
||||
# How long to ring devices for when calling in dms/groups, in seconds
|
||||
call_ring_duration = 30
|
||||
|
||||
[api.audit_logs]
|
||||
# How long audit log entries last before being removed, in seconds
|
||||
expires_after = 2592000 # 30d
|
||||
|
||||
[api.livekit.nodes]
|
||||
|
||||
[api.users]
|
||||
|
||||
@@ -260,12 +260,6 @@ pub struct ApiUsers {
|
||||
pub min_username_length: usize,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
pub struct ApiAuditLogs {
|
||||
/// How long audit log entries last before being removed, in seconds
|
||||
pub expires_after: u64,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
pub struct Api {
|
||||
pub registration: ApiRegistration,
|
||||
@@ -274,7 +268,6 @@ pub struct Api {
|
||||
pub workers: ApiWorkers,
|
||||
pub livekit: ApiLiveKit,
|
||||
pub users: ApiUsers,
|
||||
pub audit_logs: ApiAuditLogs,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
|
||||
@@ -3,16 +3,15 @@ use std::{collections::HashMap, sync::Arc};
|
||||
use futures::lock::Mutex;
|
||||
|
||||
use crate::{
|
||||
Account, AccountInvite, AuditLogEntry, Bot, Channel, ChannelCompositeKey, ChannelUnread, Emoji,
|
||||
File, FileHash, Invite, MFATicket, Member, MemberCompositeKey, Message, PolicyChange,
|
||||
RatelimitEvent, Report, Server, ServerBan, Session, Snapshot, User, UserSettings, Webhook,
|
||||
Bot, Channel, ChannelCompositeKey, ChannelUnread, Emoji, File, FileHash, Invite, Member,
|
||||
MemberCompositeKey, Message, PolicyChange, RatelimitEvent, Report, Server, ServerBan, Snapshot,
|
||||
User, UserSettings, Webhook, Account, AccountInvite, Session, MFATicket
|
||||
};
|
||||
|
||||
database_derived!(
|
||||
/// Reference implementation
|
||||
#[derive(Default, Debug)]
|
||||
pub struct ReferenceDb {
|
||||
pub audit_logs: Arc<Mutex<HashMap<String, AuditLogEntry>>>,
|
||||
pub bots: Arc<Mutex<HashMap<String, Bot>>>,
|
||||
pub channels: Arc<Mutex<HashMap<String, Channel>>>,
|
||||
pub channel_invites: Arc<Mutex<HashMap<String, Invite>>>,
|
||||
|
||||
@@ -77,78 +77,6 @@ macro_rules! auto_derived_partial {
|
||||
};
|
||||
}
|
||||
|
||||
/// Internal macro for `generate_diff!`, you should not need to use this yourself.
|
||||
macro_rules! generate_field_diff {
|
||||
(optional, $remove:ident, $fieldsmember:path, $self:ident, $before:ident, $partial:ident, $field:ident) => {
|
||||
if $partial.$field.is_some() || $remove.contains(&$fieldsmember) {
|
||||
$before.$field = $self.$field.clone();
|
||||
};
|
||||
};
|
||||
|
||||
(optional, default, $remove:ident, $fieldsmember:path, $self:ident, $before:ident, $partial:ident, $field:ident) => {
|
||||
if $partial.$field.is_some() || $remove.contains(&$fieldsmember) {
|
||||
$before.$field = Some($self.$field.clone());
|
||||
};
|
||||
};
|
||||
|
||||
($self:ident, $before:ident, $partial:ident, $field:ident) => {
|
||||
if $partial.$field.is_some() {
|
||||
$before.$field = Some($self.$field.clone());
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/// Generates a partial model containing the data which has changed in an update
|
||||
///
|
||||
/// ## Usage:
|
||||
/// `before` is the "output" containing what the model had before being updated,
|
||||
/// this will corraspond to `partial` which is what the data is being changed too.
|
||||
///
|
||||
/// ```rs
|
||||
/// let mut before = PartialModel::default();
|
||||
///
|
||||
/// generate_diff!(
|
||||
/// self, // database model
|
||||
/// before, // mutable empty partial corrasponding to the current model
|
||||
/// partial, // partial containing what is being updated
|
||||
/// remove, // slice of fields being removed
|
||||
/// (
|
||||
/// name, // regular non-nullable non-removable field
|
||||
/// (FieldsEnum::Nickname) nickname, // optional removable field
|
||||
/// ((default) FieldsEnum::Roles) roles, // optional removable field with custom default
|
||||
/// )
|
||||
/// );
|
||||
/// ```
|
||||
///
|
||||
/// See `Member::generate_diff` `Server::generate_diff` `Role::generate_diff` for full examples
|
||||
macro_rules! generate_diff {
|
||||
(
|
||||
$self:ident,
|
||||
$before:ident,
|
||||
$partial:ident,
|
||||
$remove:ident,
|
||||
(
|
||||
$(
|
||||
$(
|
||||
$(@$optional:tt)? (
|
||||
$($(@$default:tt)? (default))?
|
||||
$fieldsmember:path
|
||||
)
|
||||
)?
|
||||
$field: ident
|
||||
),*
|
||||
$(,)?
|
||||
)
|
||||
) => {
|
||||
$(
|
||||
generate_field_diff!(
|
||||
$( $($optional)? optional, $($($default)? default,)? $remove, $fieldsmember,)?
|
||||
$self, $before, $partial, $field
|
||||
);
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
mod drivers;
|
||||
pub use drivers::*;
|
||||
|
||||
@@ -187,6 +115,7 @@ pub use amqp::amqp::AMQP;
|
||||
#[cfg(feature = "voice")]
|
||||
pub mod voice;
|
||||
|
||||
|
||||
/// Utility function to check if a boolean value is false
|
||||
pub fn if_false(t: &bool) -> bool {
|
||||
!t
|
||||
|
||||
@@ -98,9 +98,6 @@ pub async fn create_database(db: &MongoDb) {
|
||||
.await
|
||||
.expect("Failed to create pubsub collection.");
|
||||
|
||||
db.create_collection("audit_logs")
|
||||
.await
|
||||
.expect("Failed to create audit_logs collection");
|
||||
db.create_collection("sessions")
|
||||
.await
|
||||
.expect("Failed to create sessions collection.");
|
||||
@@ -278,58 +275,6 @@ pub async fn create_database(db: &MongoDb) {
|
||||
.await
|
||||
.expect("Failed to create ratelimit_events index.");
|
||||
|
||||
db.run_command(doc! {
|
||||
"createIndexes": "audit_logs",
|
||||
"indexes": [
|
||||
{
|
||||
"key": {
|
||||
"expires_at": 1_i32,
|
||||
},
|
||||
"name": "expires_at_ttl",
|
||||
// We set the expire after to 0 because we store when it expires instead of when the document was inserted,
|
||||
// this is because mongo cant read the timestamp from the ulid so we need to do this workaround.
|
||||
// relevant docs: https://www.mongodb.com/docs/manual/tutorial/expire-data/#expire-documents-at-a-specific-clock-time
|
||||
"expireAfterSeconds": 0
|
||||
},
|
||||
{
|
||||
"key": {
|
||||
"server": 1_i32,
|
||||
"user": 1_i32,
|
||||
"action.type": 1_i32,
|
||||
},
|
||||
"name": "audit_log_filters",
|
||||
},
|
||||
]
|
||||
})
|
||||
.await
|
||||
.expect("Failed to create audit_logs index");
|
||||
|
||||
db.run_command(doc! {
|
||||
"createIndexes": "audit_logs",
|
||||
"indexes": [
|
||||
{
|
||||
"key": {
|
||||
"expires_at": 1_i32,
|
||||
},
|
||||
"name": "expires_at_ttl",
|
||||
// We set the expire after to 0 because we store when it expires instead of when the document was inserted,
|
||||
// this is because mongo cant read the timestamp from the ulid so we need to do this workaround.
|
||||
// relevant docs: https://www.mongodb.com/docs/manual/tutorial/expire-data/#expire-documents-at-a-specific-clock-time
|
||||
"expireAfterSeconds": 0
|
||||
},
|
||||
{
|
||||
"key": {
|
||||
"server": 1_i32,
|
||||
"user": 1_i32,
|
||||
"action.type": 1_i32,
|
||||
},
|
||||
"name": "audit_log_filters",
|
||||
},
|
||||
]
|
||||
})
|
||||
.await
|
||||
.expect("Failed to create audit_logs index");
|
||||
|
||||
db.run_command(doc! {
|
||||
"createIndexes": "accounts",
|
||||
"indexes": [
|
||||
|
||||
@@ -26,7 +26,7 @@ struct MigrationInfo {
|
||||
revision: i32,
|
||||
}
|
||||
|
||||
pub const LATEST_REVISION: i32 = 52; // MUST BE +1 to last migration
|
||||
pub const LATEST_REVISION: i32 = 51; // MUST BE +1 to last migration
|
||||
|
||||
pub async fn migrate_database(db: &MongoDb) {
|
||||
let migrations = db.col::<Document>("migrations");
|
||||
@@ -1478,56 +1478,17 @@ pub async fn run_migrations(db: &MongoDb, revision: i32) -> i32 {
|
||||
if revision <= 50 {
|
||||
info!("Running migration [revision 50 / 13-04-2026]: Rename invites collection to account_invites");
|
||||
|
||||
let result = db.db()
|
||||
db.db()
|
||||
.client()
|
||||
.database("admin")
|
||||
.run_command(doc! {
|
||||
"renameCollection": "revolt.invites",
|
||||
"to": "revolt.account_invites",
|
||||
"dropTarget": true
|
||||
})
|
||||
.await;
|
||||
|
||||
if let Err(e) = result {
|
||||
// NamespaceNotFound (26) = source collection doesn't exist, safe to ignore
|
||||
if !matches!(e.kind.as_ref(), mongodb::error::ErrorKind::Command(ce) if ce.code == 26) {
|
||||
panic!("Failed to rename invites collection: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if revision >= 51 {
|
||||
info!("Running migration [revision 51 / 28-11-2025]: Add audit logs collection");
|
||||
|
||||
db.db()
|
||||
.create_collection("audit_logs")
|
||||
.await
|
||||
.expect("Failed to create audit_logs collection");
|
||||
|
||||
db.db()
|
||||
.run_command(doc! {
|
||||
"createIndexes": "audit_logs",
|
||||
"indexes": [
|
||||
{
|
||||
"key": {
|
||||
"expires_at": 1_i32,
|
||||
},
|
||||
"name": "expires_at_ttl",
|
||||
"expireAfterSeconds": 0
|
||||
},
|
||||
{
|
||||
"key": {
|
||||
"server": 1_i32,
|
||||
"user": 1_i32,
|
||||
"action.type": 1_i32,
|
||||
},
|
||||
"name": "audit_log_filters",
|
||||
},
|
||||
]
|
||||
"renameCollection": "revolt.invites",
|
||||
"to": "revolt.account_invites",
|
||||
"dropTarget": true
|
||||
})
|
||||
.await
|
||||
.expect("Failed to create audit_logs index");
|
||||
};
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// Reminder to update LATEST_REVISION when adding new migrations.
|
||||
LATEST_REVISION.max(revision)
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
mod model;
|
||||
mod ops;
|
||||
|
||||
pub use model::*;
|
||||
pub use ops::*;
|
||||
@@ -1,275 +0,0 @@
|
||||
use std::{collections::HashSet, time::Duration};
|
||||
|
||||
use iso8601_timestamp::Timestamp;
|
||||
use revolt_config::config;
|
||||
use ulid::Ulid;
|
||||
|
||||
use crate::{Database, PartialChannel, PartialMember, PartialRole, PartialServer, User, PartialEmoji};
|
||||
use revolt_models::v0;
|
||||
use revolt_permissions::OverrideField;
|
||||
use revolt_result::Result;
|
||||
|
||||
auto_derived!(
|
||||
/// Audit log entry
|
||||
pub struct AuditLogEntry {
|
||||
/// Unique ID
|
||||
#[serde(rename = "_id")]
|
||||
pub id: String,
|
||||
|
||||
/// When the audit log entry gets auto-deleted
|
||||
///
|
||||
/// This is only stored in the database and not given to users.
|
||||
pub expires_at: Timestamp,
|
||||
|
||||
/// The server the entry happened in
|
||||
pub server: String,
|
||||
/// User provided reason
|
||||
pub reason: Option<String>,
|
||||
/// User who ran the action
|
||||
pub user: String,
|
||||
/// User this action is targetting
|
||||
pub target: Option<String>,
|
||||
/// The action ran
|
||||
pub action: AuditLogEntryAction,
|
||||
}
|
||||
|
||||
/// Indivual audit log action
|
||||
#[serde(tag = "type")]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum AuditLogEntryAction {
|
||||
MessageDelete {
|
||||
author: String,
|
||||
channel: String,
|
||||
},
|
||||
MessageBulkDelete {
|
||||
channel: String,
|
||||
count: usize,
|
||||
},
|
||||
MessagePin {
|
||||
message: String,
|
||||
author: String,
|
||||
channel: String,
|
||||
},
|
||||
MessageUnpin {
|
||||
message: String,
|
||||
author: String,
|
||||
channel: String,
|
||||
},
|
||||
BanCreate {
|
||||
user: String,
|
||||
},
|
||||
BanDelete {
|
||||
user: String,
|
||||
},
|
||||
ChannelCreate {
|
||||
channel: String,
|
||||
name: String,
|
||||
},
|
||||
ChannelEdit {
|
||||
channel: String,
|
||||
before: PartialChannel,
|
||||
after: PartialChannel,
|
||||
},
|
||||
ChannelRolePermissionsEdit {
|
||||
channel: String,
|
||||
role: String,
|
||||
permissions: OverrideField,
|
||||
},
|
||||
ChannelDelete {
|
||||
channel: String,
|
||||
name: String,
|
||||
},
|
||||
MemberEdit {
|
||||
user: String,
|
||||
before: PartialMember,
|
||||
after: PartialMember,
|
||||
},
|
||||
MemberKick {
|
||||
user: String,
|
||||
},
|
||||
ServerEdit {
|
||||
before: PartialServer,
|
||||
after: PartialServer,
|
||||
},
|
||||
RoleEdit {
|
||||
role: String,
|
||||
before: PartialRole,
|
||||
after: PartialRole,
|
||||
},
|
||||
RoleCreate {
|
||||
role: String,
|
||||
name: String,
|
||||
},
|
||||
RoleDelete {
|
||||
role: String,
|
||||
name: String,
|
||||
},
|
||||
RolesReorder {
|
||||
before: Vec<String>,
|
||||
after: Vec<String>,
|
||||
},
|
||||
InviteCreate {
|
||||
invite: String,
|
||||
channel: String,
|
||||
},
|
||||
InviteDelete {
|
||||
invite: String,
|
||||
channel: String,
|
||||
},
|
||||
WebhookCreate {
|
||||
webhook: String,
|
||||
name: String,
|
||||
channel: String,
|
||||
},
|
||||
WebhookDelete {
|
||||
webhook: String,
|
||||
name: String,
|
||||
channel: String,
|
||||
},
|
||||
EmojiCreate {
|
||||
emoji: String,
|
||||
name: String,
|
||||
},
|
||||
EmojiUpdate {
|
||||
emoji: String,
|
||||
before: PartialEmoji,
|
||||
after: PartialEmoji,
|
||||
},
|
||||
EmojiDelete {
|
||||
emoji: String,
|
||||
name: String,
|
||||
},
|
||||
}
|
||||
|
||||
/// Audit Log Query
|
||||
pub struct AuditLogQuery {
|
||||
/// Filter by who ran the action
|
||||
pub user: Option<String>,
|
||||
/// Filter by who the action is targetting
|
||||
pub target: Option<String>,
|
||||
/// Filter by the action type
|
||||
pub r#type: Option<Vec<String>>,
|
||||
/// Entries before a certain entry id
|
||||
pub before: Option<String>,
|
||||
/// Entries after a certain entry id
|
||||
pub after: Option<String>,
|
||||
/// Maximum number of entries to fetch
|
||||
pub limit: i64,
|
||||
}
|
||||
);
|
||||
|
||||
impl AuditLogEntryAction {
|
||||
// TODO: migrate this to a rabbitmq queue to avoid spawning lots of tasks
|
||||
/// Generates an `AuditLogEntry` for the current action and inserts it into the database
|
||||
pub async fn insert<R: Into<Option<String>>>(
|
||||
self,
|
||||
db: &Database,
|
||||
server: String,
|
||||
reason: R,
|
||||
user: String,
|
||||
target: Option<String>,
|
||||
) -> AuditLogEntry {
|
||||
let config = config().await;
|
||||
|
||||
let id = Ulid::new();
|
||||
let expires_at = id
|
||||
.datetime()
|
||||
.checked_add(Duration::from_secs(config.api.audit_logs.expires_after))
|
||||
.unwrap()
|
||||
.into();
|
||||
|
||||
let entry = AuditLogEntry {
|
||||
id: id.to_string(),
|
||||
expires_at,
|
||||
server,
|
||||
reason: reason.into(),
|
||||
user,
|
||||
target,
|
||||
action: self,
|
||||
};
|
||||
|
||||
// running the insert inside a task can cause race conditions in the test so for now just dont use a task for tests for now
|
||||
// this will need to be redone for when we migrate to using rabbitmq here anyway.
|
||||
#[cfg(not(test))]
|
||||
tokio::task::spawn({
|
||||
let db = db.clone();
|
||||
let entry = entry.clone();
|
||||
|
||||
async move { revolt_config::report_internal_error!(db.insert_audit_log_entry(&entry).await) }
|
||||
});
|
||||
|
||||
#[cfg(test)]
|
||||
db.insert_audit_log_entry(&entry).await.unwrap();
|
||||
|
||||
entry
|
||||
}
|
||||
}
|
||||
|
||||
impl AuditLogEntry {
|
||||
/// Fetches the corrasponding users and members for each audit log entry
|
||||
pub async fn with_users(
|
||||
db: &Database,
|
||||
server_id: &str,
|
||||
user: &User,
|
||||
entries: &[Self],
|
||||
) -> Result<(Vec<v0::User>, Vec<v0::Member>)> {
|
||||
let mut user_ids = HashSet::new();
|
||||
|
||||
for entry in entries {
|
||||
user_ids.insert(entry.user.clone());
|
||||
|
||||
match &entry.action {
|
||||
AuditLogEntryAction::MessageDelete { author, .. } => {
|
||||
user_ids.insert(author.clone());
|
||||
}
|
||||
AuditLogEntryAction::BanCreate { user } => {
|
||||
user_ids.insert(user.clone());
|
||||
}
|
||||
AuditLogEntryAction::BanDelete { user } => {
|
||||
user_ids.insert(user.clone());
|
||||
}
|
||||
AuditLogEntryAction::ChannelCreate { .. } => {}
|
||||
AuditLogEntryAction::MemberEdit { user, .. } => {
|
||||
user_ids.insert(user.clone());
|
||||
}
|
||||
AuditLogEntryAction::MemberKick { user } => {
|
||||
user_ids.insert(user.clone());
|
||||
}
|
||||
AuditLogEntryAction::MessagePin { author, .. } => {
|
||||
user_ids.insert(author.clone());
|
||||
}
|
||||
AuditLogEntryAction::MessageUnpin { author, .. } => {
|
||||
user_ids.insert(author.clone());
|
||||
}
|
||||
AuditLogEntryAction::ServerEdit { .. } => {}
|
||||
AuditLogEntryAction::RoleEdit { .. } => {}
|
||||
AuditLogEntryAction::RoleCreate { .. } => {}
|
||||
AuditLogEntryAction::RoleDelete { .. } => {}
|
||||
AuditLogEntryAction::RolesReorder { .. } => {}
|
||||
AuditLogEntryAction::MessageBulkDelete { .. } => {}
|
||||
AuditLogEntryAction::ChannelEdit { .. } => {}
|
||||
AuditLogEntryAction::ChannelRolePermissionsEdit { .. } => {}
|
||||
AuditLogEntryAction::ChannelDelete { .. } => {}
|
||||
AuditLogEntryAction::InviteCreate { .. } => {}
|
||||
AuditLogEntryAction::InviteDelete { .. } => {}
|
||||
AuditLogEntryAction::WebhookCreate { .. } => {}
|
||||
AuditLogEntryAction::WebhookDelete { .. } => {}
|
||||
AuditLogEntryAction::EmojiCreate { .. } => {}
|
||||
AuditLogEntryAction::EmojiUpdate { .. } => {}
|
||||
AuditLogEntryAction::EmojiDelete { .. } => {}
|
||||
};
|
||||
}
|
||||
|
||||
let user_ids = user_ids.into_iter().collect::<Vec<_>>();
|
||||
|
||||
let users = User::fetch_many_ids_as_mutuals(db, user, &user_ids).await?;
|
||||
let members = db
|
||||
.fetch_members(server_id, &user_ids)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect();
|
||||
|
||||
Ok((users, members))
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
use revolt_result::Result;
|
||||
|
||||
use crate::{AuditLogEntry, AuditLogQuery};
|
||||
|
||||
#[cfg(feature = "mongodb")]
|
||||
mod mongodb;
|
||||
mod reference;
|
||||
|
||||
#[async_trait]
|
||||
pub trait AbstractAuditLogs: Sync + Send {
|
||||
/// Inserts an entry into the server's audit log
|
||||
async fn insert_audit_log_entry(&self, entry: &AuditLogEntry) -> Result<()>;
|
||||
|
||||
/// Fetches a server's audit logs using the provided query options
|
||||
async fn get_server_audit_logs(
|
||||
&self,
|
||||
server: &str,
|
||||
query: AuditLogQuery,
|
||||
) -> Result<Vec<AuditLogEntry>>;
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
use mongodb::options::FindOptions;
|
||||
use revolt_result::Result;
|
||||
|
||||
use crate::{AuditLogEntry, AuditLogQuery, MongoDb};
|
||||
|
||||
use super::AbstractAuditLogs;
|
||||
|
||||
static COL: &str = "audit_logs";
|
||||
|
||||
#[async_trait]
|
||||
impl AbstractAuditLogs for MongoDb {
|
||||
/// Inserts an entry into the server's audit log
|
||||
async fn insert_audit_log_entry(&self, entry: &AuditLogEntry) -> Result<()> {
|
||||
query!(self, insert_one, COL, entry).map(|_| ())
|
||||
}
|
||||
|
||||
/// Fetches a server's audit logs using the provided query options
|
||||
async fn get_server_audit_logs(
|
||||
&self,
|
||||
server: &str,
|
||||
query: AuditLogQuery,
|
||||
) -> Result<Vec<AuditLogEntry>> {
|
||||
let mut filter = doc! {
|
||||
"server": server
|
||||
};
|
||||
|
||||
if let Some(user) = query.user {
|
||||
filter.insert("user", user);
|
||||
};
|
||||
|
||||
if let Some(target) = query.target {
|
||||
filter.insert("target", target);
|
||||
}
|
||||
|
||||
if let Some(types) = query.r#type {
|
||||
filter.insert("action.type", doc! { "$in": types });
|
||||
};
|
||||
|
||||
if let Some(doc) = match (query.before, query.after) {
|
||||
(Some(before), Some(after)) => Some(doc! {
|
||||
"$lt": before,
|
||||
"$gt": after
|
||||
}),
|
||||
(Some(before), _) => Some(doc! {
|
||||
"$lt": before
|
||||
}),
|
||||
(_, Some(after)) => Some(doc! {
|
||||
"$gt": after
|
||||
}),
|
||||
_ => None,
|
||||
} {
|
||||
filter.insert("_id", doc);
|
||||
};
|
||||
|
||||
self.find_with_options(
|
||||
COL,
|
||||
filter,
|
||||
FindOptions::builder()
|
||||
.limit(query.limit)
|
||||
.sort(doc! { "_id": -1 })
|
||||
.build(),
|
||||
)
|
||||
.await
|
||||
.map_err(|_| create_database_error!("find", COL))
|
||||
}
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
use revolt_result::Result;
|
||||
|
||||
use crate::{AuditLogEntry, AuditLogQuery, ReferenceDb};
|
||||
|
||||
use super::AbstractAuditLogs;
|
||||
|
||||
#[async_trait]
|
||||
impl AbstractAuditLogs for ReferenceDb {
|
||||
/// Inserts an entry into the server's audit log
|
||||
async fn insert_audit_log_entry(&self, entry: &AuditLogEntry) -> Result<()> {
|
||||
self.audit_logs
|
||||
.lock()
|
||||
.await
|
||||
.insert(entry.id.clone(), entry.clone());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Fetches a server's audit logs using the provided query options
|
||||
async fn get_server_audit_logs(
|
||||
&self,
|
||||
server: &str,
|
||||
query: AuditLogQuery,
|
||||
) -> Result<Vec<AuditLogEntry>> {
|
||||
let lock = self.audit_logs.lock().await;
|
||||
|
||||
let mut logs = lock
|
||||
.values()
|
||||
.filter(|entry| {
|
||||
if entry.server != server {
|
||||
return false;
|
||||
};
|
||||
|
||||
if let Some(user) = &query.user {
|
||||
if &entry.user != user {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if query.target.is_some() && entry.target != query.target {
|
||||
return false;
|
||||
}
|
||||
|
||||
if let Some(before) = &query.before {
|
||||
if &entry.id > before {
|
||||
return false;
|
||||
};
|
||||
};
|
||||
|
||||
if let Some(after) = &query.after {
|
||||
if &entry.id < after {
|
||||
return false;
|
||||
};
|
||||
};
|
||||
|
||||
if let Some(action_types) = &query.r#type {
|
||||
let entry_type = serde_json::to_value(entry.action.clone())
|
||||
.unwrap()
|
||||
.as_object()
|
||||
.unwrap()
|
||||
.get("type")
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
|
||||
if !action_types.contains(&entry_type) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
true
|
||||
})
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
logs.sort_by(|a, b| b.id.cmp(&a.id));
|
||||
logs.truncate(query.limit as usize);
|
||||
Ok(logs)
|
||||
}
|
||||
}
|
||||
@@ -161,7 +161,6 @@ auto_derived!(
|
||||
Icon,
|
||||
DefaultPermissions,
|
||||
Voice,
|
||||
Slowmode,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -555,12 +554,6 @@ impl Channel {
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
FieldsChannel::Slowmode => match self {
|
||||
Self::TextChannel { slowmode, .. } => {
|
||||
slowmode.take();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -655,122 +648,6 @@ impl Channel {
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates a PartialChannel containing the data which has changed in an update
|
||||
pub fn generate_diff(
|
||||
&self,
|
||||
partial: &PartialChannel,
|
||||
remove: &[FieldsChannel],
|
||||
) -> PartialChannel {
|
||||
let mut before = PartialChannel::default();
|
||||
|
||||
match self {
|
||||
Channel::SavedMessages { .. } => {}
|
||||
Channel::DirectMessage {
|
||||
active,
|
||||
last_message_id,
|
||||
..
|
||||
} => {
|
||||
if partial.active.is_some() {
|
||||
before.active = Some(*active);
|
||||
};
|
||||
|
||||
if partial.last_message_id.is_some() {
|
||||
before.last_message_id = last_message_id.clone()
|
||||
};
|
||||
}
|
||||
Channel::Group {
|
||||
name,
|
||||
owner,
|
||||
description,
|
||||
icon,
|
||||
last_message_id,
|
||||
permissions,
|
||||
nsfw,
|
||||
..
|
||||
} => {
|
||||
if partial.name.is_some() {
|
||||
before.name = Some(name.clone());
|
||||
};
|
||||
|
||||
if partial.owner.is_some() {
|
||||
before.owner = Some(owner.clone());
|
||||
};
|
||||
|
||||
if partial.description.is_some() || remove.contains(&FieldsChannel::Description) {
|
||||
before.description = description.clone();
|
||||
};
|
||||
|
||||
if partial.icon.is_some() || remove.contains(&FieldsChannel::Icon) {
|
||||
before.icon = icon.clone();
|
||||
};
|
||||
|
||||
if partial.last_message_id.is_some() {
|
||||
before.last_message_id = last_message_id.clone()
|
||||
};
|
||||
|
||||
if partial.permissions.is_some() {
|
||||
before.permissions = *permissions;
|
||||
};
|
||||
|
||||
if partial.nsfw.is_some() {
|
||||
before.nsfw = Some(*nsfw);
|
||||
};
|
||||
}
|
||||
Channel::TextChannel {
|
||||
name,
|
||||
description,
|
||||
icon,
|
||||
last_message_id,
|
||||
default_permissions,
|
||||
role_permissions,
|
||||
nsfw,
|
||||
voice,
|
||||
slowmode,
|
||||
..
|
||||
} => {
|
||||
if partial.name.is_some() {
|
||||
before.name = Some(name.clone());
|
||||
};
|
||||
|
||||
if partial.description.is_some() || remove.contains(&FieldsChannel::Description) {
|
||||
before.description = description.clone();
|
||||
};
|
||||
|
||||
if partial.icon.is_some() || remove.contains(&FieldsChannel::Icon) {
|
||||
before.icon = icon.clone();
|
||||
};
|
||||
|
||||
if partial.last_message_id.is_some() {
|
||||
before.last_message_id = last_message_id.clone()
|
||||
};
|
||||
|
||||
if partial.default_permissions.is_some()
|
||||
|| remove.contains(&FieldsChannel::DefaultPermissions)
|
||||
{
|
||||
before.default_permissions = *default_permissions;
|
||||
};
|
||||
|
||||
if partial.role_permissions.is_some() {
|
||||
before.role_permissions = Some(role_permissions.clone());
|
||||
};
|
||||
|
||||
if partial.nsfw.is_some() {
|
||||
before.nsfw = Some(*nsfw);
|
||||
};
|
||||
|
||||
if partial.voice.is_some() || remove.contains(&FieldsChannel::Voice) {
|
||||
before.voice = voice.clone();
|
||||
};
|
||||
|
||||
if partial.slowmode.is_some() {
|
||||
before.slowmode = *slowmode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
before
|
||||
}
|
||||
|
||||
/// Acknowledge a message
|
||||
pub async fn ack(&self, user: &str, message: &str, amqp: &AMQP) -> Result<()> {
|
||||
EventV1::ChannelAck {
|
||||
@@ -900,7 +777,6 @@ impl IntoDocumentPath for FieldsChannel {
|
||||
FieldsChannel::Icon => "icon",
|
||||
FieldsChannel::DefaultPermissions => "default_permissions",
|
||||
FieldsChannel::Voice => "voice",
|
||||
FieldsChannel::Slowmode => "slowmode",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ static PERMISSIBLE_EMOJIS: Lazy<HashSet<String>> = Lazy::new(|| {
|
||||
.collect()
|
||||
});
|
||||
|
||||
auto_derived_partial!(
|
||||
auto_derived!(
|
||||
/// Emoji
|
||||
pub struct Emoji {
|
||||
/// Unique Id
|
||||
@@ -34,17 +34,20 @@ auto_derived_partial!(
|
||||
/// Whether the emoji is marked as nsfw
|
||||
#[serde(skip_serializing_if = "crate::if_false", default)]
|
||||
pub nsfw: bool,
|
||||
},
|
||||
"PartialEmoji"
|
||||
);
|
||||
}
|
||||
|
||||
auto_derived!(
|
||||
/// Parent Id of the emoji
|
||||
#[serde(tag = "type")]
|
||||
pub enum EmojiParent {
|
||||
Server { id: String },
|
||||
Detached,
|
||||
}
|
||||
|
||||
/// Partial representation of an emoji
|
||||
pub struct PartialEmoji {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub name: Option<String>,
|
||||
}
|
||||
);
|
||||
|
||||
#[allow(clippy::disallowed_methods)]
|
||||
@@ -69,14 +72,14 @@ impl Emoji {
|
||||
}
|
||||
|
||||
/// Delete an emoji
|
||||
pub async fn delete(&self, db: &Database) -> Result<()> {
|
||||
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
|
||||
db.detach_emoji(&self).await
|
||||
}
|
||||
|
||||
/// Update an emoji
|
||||
@@ -109,18 +112,4 @@ impl Emoji {
|
||||
Ok(PERMISSIBLE_EMOJIS.contains(&sanitized_emoji))
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates a PartialEmoji containing the data which has changed in an update
|
||||
pub fn generate_diff(&self, partial: &PartialEmoji) -> PartialEmoji {
|
||||
let mut before = PartialEmoji::default();
|
||||
|
||||
generate_diff!(
|
||||
self, before, partial, remove,
|
||||
(
|
||||
name,
|
||||
)
|
||||
);
|
||||
|
||||
before
|
||||
}
|
||||
}
|
||||
|
||||
@@ -998,13 +998,11 @@ impl Message {
|
||||
}
|
||||
|
||||
/// Delete a message
|
||||
pub async fn delete(&self, db: &Database) -> Result<()> {
|
||||
let file_ids = self
|
||||
pub async fn delete(self, db: &Database) -> Result<()> {
|
||||
let file_ids: Vec<String> = self
|
||||
.attachments
|
||||
.iter()
|
||||
.flatten()
|
||||
.map(|file| file.id.clone())
|
||||
.collect::<Vec<_>>();
|
||||
.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?;
|
||||
@@ -1013,10 +1011,10 @@ impl Message {
|
||||
db.delete_message(&self.id).await?;
|
||||
|
||||
EventV1::MessageDelete {
|
||||
id: self.id.clone(),
|
||||
id: self.id,
|
||||
channel: self.channel.clone(),
|
||||
}
|
||||
.p(self.channel.clone())
|
||||
.p(self.channel)
|
||||
.await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
mod admin_migrations;
|
||||
mod audit_logs;
|
||||
mod bots;
|
||||
mod channel_invites;
|
||||
mod channel_unreads;
|
||||
@@ -24,7 +23,6 @@ mod sessions;
|
||||
mod mfa_tickets;
|
||||
|
||||
pub use admin_migrations::*;
|
||||
pub use audit_logs::*;
|
||||
pub use bots::*;
|
||||
pub use channel_invites::*;
|
||||
pub use channel_unreads::*;
|
||||
@@ -57,7 +55,6 @@ pub trait AbstractDatabase:
|
||||
Sync
|
||||
+ Send
|
||||
+ admin_migrations::AbstractMigrations
|
||||
+ audit_logs::AbstractAuditLogs
|
||||
+ bots::AbstractBots
|
||||
+ channels::AbstractChannels
|
||||
+ channel_invites::AbstractChannelInvites
|
||||
|
||||
@@ -245,26 +245,6 @@ impl Member {
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates a PartialMember containing the data which has changed in an update
|
||||
pub fn generate_diff(&self, partial: &PartialMember, remove: &[FieldsMember]) -> PartialMember {
|
||||
let mut before = PartialMember::default();
|
||||
|
||||
generate_diff!(
|
||||
self, before, partial, remove,
|
||||
(
|
||||
(FieldsMember::Nickname) nickname,
|
||||
(FieldsMember::Avatar) avatar,
|
||||
(FieldsMember::Timeout) timeout,
|
||||
(FieldsMember::Pronouns) pronouns,
|
||||
((default) FieldsMember::Roles) roles,
|
||||
((default) FieldsMember::CanPublish) can_publish,
|
||||
((default) FieldsMember::CanReceive) can_receive,
|
||||
)
|
||||
);
|
||||
|
||||
before
|
||||
}
|
||||
|
||||
/// Get this user's current ranking
|
||||
pub fn get_ranking(&self, server: &Server) -> i64 {
|
||||
let mut value = i64::MAX;
|
||||
@@ -290,7 +270,7 @@ impl Member {
|
||||
|
||||
/// Remove member from server
|
||||
pub async fn remove(
|
||||
&self,
|
||||
self,
|
||||
db: &Database,
|
||||
server: &Server,
|
||||
intention: RemovalIntention,
|
||||
@@ -317,9 +297,9 @@ impl Member {
|
||||
})
|
||||
{
|
||||
match intention {
|
||||
RemovalIntention::Leave => SystemMessage::UserLeft { id: self.id.user.clone() },
|
||||
RemovalIntention::Kick => SystemMessage::UserKicked { id: self.id.user.clone() },
|
||||
RemovalIntention::Ban => SystemMessage::UserBanned { id: self.id.user.clone() },
|
||||
RemovalIntention::Leave => SystemMessage::UserLeft { id: self.id.user },
|
||||
RemovalIntention::Kick => SystemMessage::UserKicked { id: self.id.user },
|
||||
RemovalIntention::Ban => SystemMessage::UserBanned { id: self.id.user },
|
||||
}
|
||||
.into_message(id.to_string())
|
||||
// TODO: support notifications here in the future?
|
||||
|
||||
@@ -235,31 +235,6 @@ impl Server {
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates a PartialServer containing the data which has changed in an update
|
||||
pub fn generate_diff(&self, partial: &PartialServer, remove: &[FieldsServer]) -> PartialServer {
|
||||
let mut before = PartialServer::default();
|
||||
|
||||
generate_diff!(
|
||||
self, before, partial, remove,
|
||||
(
|
||||
owner,
|
||||
name,
|
||||
(FieldsServer::Description) description,
|
||||
(FieldsServer::Categories) categories,
|
||||
(FieldsServer::SystemMessages) system_messages,
|
||||
roles,
|
||||
default_permissions,
|
||||
(FieldsServer::Icon) icon,
|
||||
(FieldsServer::Banner) banner,
|
||||
nsfw,
|
||||
analytics,
|
||||
discoverable,
|
||||
)
|
||||
);
|
||||
|
||||
before
|
||||
}
|
||||
|
||||
/// Ordered roles list
|
||||
pub fn ordered_roles(&self) -> Vec<(String, Role)> {
|
||||
let mut ordered_roles = self.roles.clone().into_iter().collect::<Vec<_>>();
|
||||
@@ -402,27 +377,8 @@ impl Role {
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates a PartialRole containing the data which has changed in an update
|
||||
pub fn generate_diff(&self, partial: &PartialRole, remove: &[FieldsRole]) -> PartialRole {
|
||||
let mut before = PartialRole::default();
|
||||
|
||||
generate_diff!(
|
||||
self, before, partial, remove,
|
||||
(
|
||||
name,
|
||||
permissions,
|
||||
(FieldsRole::Colour) colour,
|
||||
hoist,
|
||||
rank,
|
||||
(FieldsRole::Icon) icon,
|
||||
)
|
||||
);
|
||||
|
||||
before
|
||||
}
|
||||
|
||||
/// Delete a role
|
||||
pub async fn delete(&self, db: &Database, server_id: &str) -> Result<()> {
|
||||
pub async fn delete(self, db: &Database, server_id: &str) -> Result<()> {
|
||||
EventV1::ServerRoleDelete {
|
||||
id: server_id.to_string(),
|
||||
role_id: self.id.clone(),
|
||||
|
||||
@@ -259,13 +259,6 @@ impl MongoDb {
|
||||
})
|
||||
.await?;
|
||||
|
||||
self.col::<Document>("audit_logs")
|
||||
.delete_many(doc! {
|
||||
"server": &server_id
|
||||
})
|
||||
.await
|
||||
.map_err(|_| create_database_error!("delete_many", "audit_logs"))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -319,7 +319,6 @@ impl From<FieldsChannel> for crate::FieldsChannel {
|
||||
FieldsChannel::Icon => crate::FieldsChannel::Icon,
|
||||
FieldsChannel::DefaultPermissions => crate::FieldsChannel::DefaultPermissions,
|
||||
FieldsChannel::Voice => crate::FieldsChannel::Voice,
|
||||
FieldsChannel::Slowmode => crate::FieldsChannel::Slowmode,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -331,7 +330,6 @@ impl From<crate::FieldsChannel> for FieldsChannel {
|
||||
crate::FieldsChannel::Icon => FieldsChannel::Icon,
|
||||
crate::FieldsChannel::DefaultPermissions => FieldsChannel::DefaultPermissions,
|
||||
crate::FieldsChannel::Voice => FieldsChannel::Voice,
|
||||
crate::FieldsChannel::Slowmode => FieldsChannel::Slowmode,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1423,14 +1421,6 @@ impl From<FieldsMessage> for crate::FieldsMessage {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<crate::VoiceInformation> for VoiceInformation {
|
||||
fn from(value: crate::VoiceInformation) -> Self {
|
||||
VoiceInformation {
|
||||
max_users: value.max_users,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<VoiceInformation> for crate::VoiceInformation {
|
||||
fn from(value: VoiceInformation) -> Self {
|
||||
crate::VoiceInformation {
|
||||
@@ -1439,151 +1429,10 @@ impl From<VoiceInformation> for crate::VoiceInformation {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<crate::AuditLogEntryAction> for AuditLogEntryAction {
|
||||
fn from(value: crate::AuditLogEntryAction) -> Self {
|
||||
match value {
|
||||
crate::AuditLogEntryAction::MessageDelete { author, channel } => {
|
||||
AuditLogEntryAction::MessageDelete { author, channel }
|
||||
}
|
||||
crate::AuditLogEntryAction::BanCreate { user } => {
|
||||
AuditLogEntryAction::BanCreate { user }
|
||||
}
|
||||
crate::AuditLogEntryAction::BanDelete { user } => {
|
||||
AuditLogEntryAction::BanDelete { user }
|
||||
}
|
||||
crate::AuditLogEntryAction::ChannelCreate { channel, name } => {
|
||||
AuditLogEntryAction::ChannelCreate { channel, name }
|
||||
}
|
||||
crate::AuditLogEntryAction::MemberEdit {
|
||||
user,
|
||||
before,
|
||||
after,
|
||||
} => AuditLogEntryAction::MemberEdit {
|
||||
user,
|
||||
before: before.into(),
|
||||
after: after.into(),
|
||||
},
|
||||
crate::AuditLogEntryAction::MemberKick { user } => {
|
||||
AuditLogEntryAction::MemberKick { user }
|
||||
}
|
||||
crate::AuditLogEntryAction::ServerEdit { before, after } => {
|
||||
AuditLogEntryAction::ServerEdit {
|
||||
before: before.into(),
|
||||
after: after.into(),
|
||||
}
|
||||
}
|
||||
crate::AuditLogEntryAction::RoleEdit {
|
||||
role,
|
||||
before,
|
||||
after,
|
||||
} => AuditLogEntryAction::RoleEdit {
|
||||
role,
|
||||
before: before.into(),
|
||||
after: after.into(),
|
||||
},
|
||||
crate::AuditLogEntryAction::RoleCreate { role, name } => {
|
||||
AuditLogEntryAction::RoleCreate { role, name }
|
||||
}
|
||||
crate::AuditLogEntryAction::RoleDelete { role, name } => {
|
||||
AuditLogEntryAction::RoleDelete { role, name }
|
||||
}
|
||||
crate::AuditLogEntryAction::RolesReorder { before, after } => {
|
||||
AuditLogEntryAction::RolesReorder { before, after }
|
||||
}
|
||||
crate::AuditLogEntryAction::MessageBulkDelete { channel, count } => {
|
||||
AuditLogEntryAction::MessageBulkDelete { channel, count }
|
||||
}
|
||||
crate::AuditLogEntryAction::ChannelEdit {
|
||||
channel,
|
||||
before,
|
||||
after,
|
||||
} => AuditLogEntryAction::ChannelEdit {
|
||||
channel,
|
||||
before: before.into(),
|
||||
after: after.into(),
|
||||
},
|
||||
crate::AuditLogEntryAction::ChannelRolePermissionsEdit {
|
||||
channel,
|
||||
role,
|
||||
permissions,
|
||||
} => AuditLogEntryAction::ChannelRolePermissionsEdit {
|
||||
channel,
|
||||
role,
|
||||
permissions: permissions.into(),
|
||||
},
|
||||
crate::AuditLogEntryAction::ChannelDelete { channel, name } => {
|
||||
AuditLogEntryAction::ChannelDelete { channel, name }
|
||||
}
|
||||
crate::AuditLogEntryAction::InviteDelete { invite, channel } => {
|
||||
AuditLogEntryAction::InviteDelete { invite, channel }
|
||||
}
|
||||
crate::AuditLogEntryAction::WebhookCreate {
|
||||
webhook,
|
||||
name,
|
||||
channel,
|
||||
} => AuditLogEntryAction::WebhookCreate {
|
||||
webhook,
|
||||
name,
|
||||
channel,
|
||||
},
|
||||
crate::AuditLogEntryAction::WebhookDelete {
|
||||
webhook,
|
||||
name,
|
||||
channel,
|
||||
} => AuditLogEntryAction::WebhookDelete {
|
||||
webhook,
|
||||
name,
|
||||
channel,
|
||||
},
|
||||
crate::AuditLogEntryAction::EmojiCreate { emoji, name } => {
|
||||
AuditLogEntryAction::EmojiCreate { emoji, name }
|
||||
}
|
||||
crate::AuditLogEntryAction::EmojiUpdate {
|
||||
emoji,
|
||||
before,
|
||||
after,
|
||||
} => AuditLogEntryAction::EmojiUpdate {
|
||||
emoji,
|
||||
before: before.into(),
|
||||
after: after.into(),
|
||||
},
|
||||
crate::AuditLogEntryAction::EmojiDelete { emoji, name } => {
|
||||
AuditLogEntryAction::EmojiDelete { emoji, name }
|
||||
}
|
||||
crate::AuditLogEntryAction::MessagePin {
|
||||
message,
|
||||
author,
|
||||
channel,
|
||||
} => AuditLogEntryAction::MessagePin {
|
||||
message,
|
||||
author,
|
||||
channel,
|
||||
},
|
||||
crate::AuditLogEntryAction::MessageUnpin {
|
||||
message,
|
||||
author,
|
||||
channel,
|
||||
} => AuditLogEntryAction::MessageUnpin {
|
||||
message,
|
||||
author,
|
||||
channel,
|
||||
},
|
||||
crate::AuditLogEntryAction::InviteCreate { invite, channel } => {
|
||||
AuditLogEntryAction::InviteCreate { invite, channel }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<crate::AuditLogEntry> for AuditLogEntry {
|
||||
fn from(value: crate::AuditLogEntry) -> Self {
|
||||
AuditLogEntry {
|
||||
id: value.id,
|
||||
server: value.server,
|
||||
reason: value.reason,
|
||||
user: value.user,
|
||||
target: value.target,
|
||||
action: value.action.into(),
|
||||
impl From<crate::VoiceInformation> for VoiceInformation {
|
||||
fn from(value: crate::VoiceInformation) -> Self {
|
||||
VoiceInformation {
|
||||
max_users: value.max_users,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1676,9 +1525,3 @@ impl From<WebPushSubscription> for crate::WebPushSubscription {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<crate::PartialEmoji> for PartialEmoji {
|
||||
fn from(value: crate::PartialEmoji) -> Self {
|
||||
PartialEmoji { name: value.name }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,17 +159,4 @@ impl VoiceClient {
|
||||
.await
|
||||
.to_internal_error()
|
||||
}
|
||||
|
||||
pub async fn get_room_participants(
|
||||
&self,
|
||||
node: &str,
|
||||
channel_id: &str,
|
||||
) -> Result<Vec<ParticipantInfo>> {
|
||||
let room = self.get_node(node)?;
|
||||
|
||||
room.client
|
||||
.list_participants(channel_id)
|
||||
.await
|
||||
.to_internal_error()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,163 +0,0 @@
|
||||
use crate::v0::{Member, PartialChannel, PartialEmoji, PartialMember, PartialRole, PartialServer, User};
|
||||
use revolt_permissions::Override;
|
||||
|
||||
auto_derived!(
|
||||
/// Audit log entry
|
||||
pub struct AuditLogEntry {
|
||||
/// Unique ID
|
||||
#[serde(rename = "_id")]
|
||||
pub id: String,
|
||||
|
||||
/// The server the entry happened in
|
||||
pub server: String,
|
||||
/// User provided reason
|
||||
pub reason: Option<String>,
|
||||
/// User who ran the action
|
||||
pub user: String,
|
||||
/// User this action is targetting
|
||||
pub target: Option<String>,
|
||||
/// The action ran
|
||||
pub action: AuditLogEntryAction,
|
||||
}
|
||||
|
||||
/// Indivual action stored on the audit log
|
||||
#[serde(tag = "type")]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum AuditLogEntryAction {
|
||||
MessageDelete {
|
||||
author: String,
|
||||
channel: String,
|
||||
},
|
||||
MessageBulkDelete {
|
||||
channel: String,
|
||||
count: usize,
|
||||
},
|
||||
MessagePin {
|
||||
message: String,
|
||||
author: String,
|
||||
channel: String,
|
||||
},
|
||||
MessageUnpin {
|
||||
message: String,
|
||||
author: String,
|
||||
channel: String,
|
||||
},
|
||||
BanCreate {
|
||||
user: String,
|
||||
},
|
||||
BanDelete {
|
||||
user: String,
|
||||
},
|
||||
ChannelCreate {
|
||||
channel: String,
|
||||
name: String,
|
||||
},
|
||||
ChannelEdit {
|
||||
channel: String,
|
||||
before: PartialChannel,
|
||||
after: PartialChannel,
|
||||
},
|
||||
ChannelRolePermissionsEdit {
|
||||
channel: String,
|
||||
role: String,
|
||||
permissions: Override,
|
||||
},
|
||||
ChannelDelete {
|
||||
channel: String,
|
||||
name: String,
|
||||
},
|
||||
MemberEdit {
|
||||
user: String,
|
||||
before: PartialMember,
|
||||
after: PartialMember,
|
||||
},
|
||||
MemberKick {
|
||||
user: String,
|
||||
},
|
||||
ServerEdit {
|
||||
before: PartialServer,
|
||||
after: PartialServer,
|
||||
},
|
||||
RoleEdit {
|
||||
role: String,
|
||||
before: PartialRole,
|
||||
after: PartialRole,
|
||||
},
|
||||
RoleCreate {
|
||||
role: String,
|
||||
name: String,
|
||||
},
|
||||
RoleDelete {
|
||||
role: String,
|
||||
name: String,
|
||||
},
|
||||
RolesReorder {
|
||||
before: Vec<String>,
|
||||
after: Vec<String>,
|
||||
},
|
||||
InviteCreate {
|
||||
invite: String,
|
||||
channel: String,
|
||||
},
|
||||
InviteDelete {
|
||||
invite: String,
|
||||
channel: String,
|
||||
},
|
||||
WebhookCreate {
|
||||
webhook: String,
|
||||
name: String,
|
||||
channel: String,
|
||||
},
|
||||
WebhookDelete {
|
||||
webhook: String,
|
||||
name: String,
|
||||
channel: String,
|
||||
},
|
||||
EmojiCreate {
|
||||
emoji: String,
|
||||
name: String,
|
||||
},
|
||||
EmojiUpdate {
|
||||
emoji: String,
|
||||
before: PartialEmoji,
|
||||
after: PartialEmoji,
|
||||
},
|
||||
EmojiDelete {
|
||||
emoji: String,
|
||||
name: String,
|
||||
},
|
||||
}
|
||||
|
||||
/// Audit log query filters
|
||||
#[cfg_attr(feature = "validator", derive(validator::Validate))]
|
||||
#[cfg_attr(feature = "rocket", derive(rocket::FromForm))]
|
||||
pub struct OptionsAuditLogQuery {
|
||||
/// Filter by who ran the action
|
||||
#[cfg_attr(feature = "validator", validate(length(min = 26, max = 26)))]
|
||||
pub user: Option<String>,
|
||||
/// Filter by who the action is targetting
|
||||
#[cfg_attr(feature = "validator", validate(length(min = 26, max = 26)))]
|
||||
pub target: Option<String>,
|
||||
/// Filter by the action type
|
||||
pub r#type: Option<Vec<String>>,
|
||||
/// Entries before a certain entry id
|
||||
#[cfg_attr(feature = "validator", validate(length(min = 26, max = 26)))]
|
||||
pub before: Option<String>,
|
||||
/// Entries after a certain entry id
|
||||
#[cfg_attr(feature = "validator", validate(length(min = 26, max = 26)))]
|
||||
pub after: Option<String>,
|
||||
/// Maximum number of entries to fetch
|
||||
#[cfg_attr(feature = "validator", validate(range(min = 1, max = 100)))]
|
||||
pub limit: Option<i64>,
|
||||
}
|
||||
|
||||
/// Response containing the audit log entries and the users involved
|
||||
pub struct AuditLogQueryResponse {
|
||||
/// List of audit logs
|
||||
pub audit_logs: Vec<AuditLogEntry>,
|
||||
/// List of users
|
||||
pub users: Vec<User>,
|
||||
/// List of members
|
||||
pub members: Vec<Member>,
|
||||
}
|
||||
);
|
||||
@@ -164,7 +164,6 @@ auto_derived!(
|
||||
Icon,
|
||||
DefaultPermissions,
|
||||
Voice,
|
||||
Slowmode,
|
||||
}
|
||||
|
||||
/// New webhook information
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
mod audit_logs;
|
||||
mod bots;
|
||||
mod channel_invites;
|
||||
mod channel_unreads;
|
||||
@@ -19,7 +18,6 @@ mod accounts;
|
||||
mod mfa_tickets;
|
||||
mod sessions;
|
||||
|
||||
pub use audit_logs::*;
|
||||
pub use bots::*;
|
||||
pub use channel_invites::*;
|
||||
pub use channel_unreads::*;
|
||||
|
||||
@@ -100,11 +100,8 @@ pub enum ChannelPermission {
|
||||
/// Mention roles
|
||||
MentionRoles = 1 << 38,
|
||||
|
||||
/// Access server audit logs
|
||||
ViewAuditLogs = 1 << 40,
|
||||
|
||||
// * Misc. permissions
|
||||
// % Bits 41 to 52: free area
|
||||
// % Bits 39 to 52: free area
|
||||
// % Bits 53 to 64: do not use
|
||||
|
||||
// * Grant all permissions
|
||||
|
||||
@@ -90,7 +90,6 @@ impl IntoResponse for Error {
|
||||
ErrorType::UnknownNode => StatusCode::BAD_REQUEST,
|
||||
ErrorType::InvalidFlagValue => StatusCode::BAD_REQUEST,
|
||||
ErrorType::FeatureDisabled { .. } => StatusCode::BAD_REQUEST,
|
||||
ErrorType::HeaderTooLarge => StatusCode::BAD_REQUEST,
|
||||
|
||||
ErrorType::ProxyError => StatusCode::BAD_REQUEST,
|
||||
ErrorType::FileTooSmall => StatusCode::UNPROCESSABLE_ENTITY,
|
||||
|
||||
@@ -164,7 +164,6 @@ pub enum ErrorType {
|
||||
FailedValidation {
|
||||
error: String,
|
||||
},
|
||||
HeaderTooLarge,
|
||||
OperationFailed,
|
||||
IncorrectData {
|
||||
with: String,
|
||||
|
||||
@@ -91,7 +91,6 @@ impl<'r> Responder<'r, 'static> for Error {
|
||||
ErrorType::NotConnected => Status::BadRequest,
|
||||
ErrorType::UnknownNode => Status::BadRequest,
|
||||
ErrorType::FeatureDisabled { .. } => Status::BadRequest,
|
||||
ErrorType::HeaderTooLarge => Status::BadRequest,
|
||||
|
||||
ErrorType::ProxyError => Status::BadRequest,
|
||||
ErrorType::FileTooSmall => Status::UnprocessableEntity,
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
use livekit_api::{access_token::TokenVerifier, webhooks::WebhookReceiver};
|
||||
use livekit_protocol::TrackType;
|
||||
use revolt_database::{
|
||||
AMQP, Database, PartialMessage, SystemMessage, events::client::EventV1, iso8601_timestamp::{Duration, Timestamp}, util::reference::Reference, voice::{
|
||||
RoomMetadata, UserVoiceChannel, VoiceClient, create_voice_state, delete_channel_voice_state, delete_voice_state, get_call_notification_recipients, get_user_moved_from_voice, get_user_moved_to_voice, get_voice_channel_members, set_channel_call_started_system_message, take_channel_call_started_system_message, update_voice_state_tracks
|
||||
}
|
||||
events::client::EventV1,
|
||||
iso8601_timestamp::{Duration, Timestamp},
|
||||
util::reference::Reference,
|
||||
voice::{
|
||||
create_voice_state, delete_channel_voice_state, delete_voice_state,
|
||||
get_user_moved_from_voice, get_user_moved_to_voice, update_voice_state_tracks,
|
||||
RoomMetadata, UserVoiceChannel, VoiceClient,
|
||||
},
|
||||
Database, AMQP,
|
||||
};
|
||||
use revolt_models::v0;
|
||||
use revolt_result::{Result, ToRevoltError};
|
||||
use rocket::{post, State};
|
||||
use rocket_empty::EmptyResponse;
|
||||
use ulid::Ulid;
|
||||
|
||||
use crate::guard::AuthHeader;
|
||||
|
||||
@@ -17,12 +21,12 @@ use crate::guard::AuthHeader;
|
||||
pub async fn ingress(
|
||||
db: &State<Database>,
|
||||
voice_client: &State<VoiceClient>,
|
||||
amqp: &State<AMQP>,
|
||||
_amqp: &State<AMQP>,
|
||||
node: &str,
|
||||
auth_header: AuthHeader<'_>,
|
||||
body: &str,
|
||||
) -> Result<EmptyResponse> {
|
||||
log::debug!("received event: {body}");
|
||||
log::debug!("received event: {body:?}");
|
||||
|
||||
let config = revolt_config::config().await;
|
||||
|
||||
@@ -59,18 +63,16 @@ pub async fn ingress(
|
||||
let channel_id = channel_id.to_internal_error()?;
|
||||
let user_id = user_id.to_internal_error()?;
|
||||
let server_id = room_metadata.to_internal_error()?.server;
|
||||
let voice_channel = UserVoiceChannel {
|
||||
let channel = UserVoiceChannel {
|
||||
id: channel_id.clone(),
|
||||
server_id: server_id.clone(),
|
||||
};
|
||||
|
||||
let channel = Reference::from_unchecked(channel_id).as_channel(db).await?;
|
||||
|
||||
let joined_at = Timestamp::UNIX_EPOCH
|
||||
.checked_add(Duration::seconds(event.created_at))
|
||||
.unwrap();
|
||||
|
||||
let voice_state = create_voice_state(&voice_channel, user_id, joined_at).await?;
|
||||
let voice_state = create_voice_state(&channel, user_id, joined_at).await?;
|
||||
|
||||
// Only publish one event when a user is moved from one channel to another.
|
||||
if let Some(moved_from) = get_user_moved_to_voice(channel_id, user_id).await? {
|
||||
@@ -91,66 +93,63 @@ pub async fn ingress(
|
||||
.await;
|
||||
};
|
||||
|
||||
let participants = voice_client.get_room_participants(node, channel_id).await?;
|
||||
// TODO: fix `num_participants` being incorrect sometimes see (#457)
|
||||
// First user who joined - send call started system message.
|
||||
// if event.room.as_ref().unwrap().num_participants == 1 {
|
||||
// let user = Reference::from_unchecked(user_id).as_user(db).await?;
|
||||
|
||||
if participants.len() == 1 {
|
||||
let user = Reference::from_unchecked(user_id).as_user(db).await?;
|
||||
let message_id = Ulid::from_datetime(
|
||||
Timestamp::UNIX_EPOCH
|
||||
.checked_add(Duration::seconds(event.created_at))
|
||||
.unwrap()
|
||||
.into(),
|
||||
)
|
||||
.to_string();
|
||||
// let message_id =
|
||||
// Ulid::from_datetime(DateTime::from_timestamp_secs(event.created_at).unwrap())
|
||||
// .to_string();
|
||||
|
||||
let mut call_started_message = SystemMessage::CallStarted {
|
||||
by: user_id.to_string(),
|
||||
finished_at: None,
|
||||
}
|
||||
.into_message(channel_id.clone());
|
||||
// let mut call_started_message = SystemMessage::CallStarted {
|
||||
// by: user_id.to_string(),
|
||||
// finished_at: None,
|
||||
// }
|
||||
// .into_message(channel.id().to_string());
|
||||
|
||||
call_started_message.id = message_id;
|
||||
// call_started_message.id = message_id;
|
||||
|
||||
set_channel_call_started_system_message(channel_id, &call_started_message.id)
|
||||
.await?;
|
||||
// set_channel_call_started_system_message(channel.id(), &call_started_message.id)
|
||||
// .await?;
|
||||
|
||||
call_started_message
|
||||
.send(
|
||||
db,
|
||||
Some(amqp),
|
||||
v0::MessageAuthor::System {
|
||||
username: &user.username,
|
||||
avatar: user.avatar.as_ref().map(|file| file.id.as_ref()),
|
||||
},
|
||||
None,
|
||||
None,
|
||||
&channel,
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
// call_started_message
|
||||
// .send(
|
||||
// db,
|
||||
// Some(amqp),
|
||||
// v0::MessageAuthor::System {
|
||||
// username: &user.username,
|
||||
// avatar: user.avatar.as_ref().map(|file| file.id.as_ref()),
|
||||
// },
|
||||
// None,
|
||||
// None,
|
||||
// &channel,
|
||||
// false,
|
||||
// )
|
||||
// .await?;
|
||||
|
||||
let recipients = get_call_notification_recipients(channel_id, user_id).await?;
|
||||
let now = joined_at.format_short().to_string();
|
||||
// let recipients = get_call_notification_recipients(&channel_id, &user_id).await?;
|
||||
// let now = joined_at.format_short().to_string();
|
||||
|
||||
if let Err(e) = amqp
|
||||
.dm_call_updated(&user.id, channel_id, Some(&now), false, recipients)
|
||||
.await
|
||||
{
|
||||
revolt_config::capture_error(&e);
|
||||
}
|
||||
}
|
||||
// if let Err(e) = amqp
|
||||
// .dm_call_updated(&user.id, channel.id(), Some(&now), false, recipients)
|
||||
// .await
|
||||
// {
|
||||
// revolt_config::capture_error(&e);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
// User left a channel
|
||||
"participant_left" => {
|
||||
let channel_id = channel_id.to_internal_error()?;
|
||||
let user_id = user_id.to_internal_error()?;
|
||||
let server_id = room_metadata.to_internal_error()?.server;
|
||||
let voice_channel = UserVoiceChannel {
|
||||
let channel = UserVoiceChannel {
|
||||
id: channel_id.clone(),
|
||||
server_id: server_id.clone(),
|
||||
};
|
||||
|
||||
delete_voice_state(&voice_channel, user_id).await?;
|
||||
delete_voice_state(&channel, user_id).await?;
|
||||
|
||||
// Dont send leave event when a user is moved
|
||||
if get_user_moved_from_voice(channel_id, user_id)
|
||||
@@ -165,47 +164,49 @@ pub async fn ingress(
|
||||
.await;
|
||||
};
|
||||
|
||||
// See above for why this is commented out
|
||||
|
||||
// // Update CallStarted system message if everyone has left with the end time
|
||||
let members = get_voice_channel_members(&voice_channel).await?;
|
||||
// let members = get_voice_channel_members(channel_id).await?;
|
||||
|
||||
if members.is_none_or(|m| m.is_empty()) {
|
||||
// The channel is empty so send out an "end" message for ringing
|
||||
if let Err(e) = amqp
|
||||
.dm_call_updated(user_id, channel_id, None, true, None)
|
||||
.await
|
||||
{
|
||||
revolt_config::capture_internal_error!(&e);
|
||||
}
|
||||
// if members.is_none_or(|m| m.is_empty()) {
|
||||
// // The channel is empty so send out an "end" message for ringing
|
||||
// if let Err(e) = amqp
|
||||
// .dm_call_updated(user_id, channel_id, None, true, None)
|
||||
// .await
|
||||
// {
|
||||
// revolt_config::capture_internal_error!(&e);
|
||||
// }
|
||||
|
||||
if let Some(system_message_id) =
|
||||
take_channel_call_started_system_message(channel_id).await?
|
||||
{
|
||||
// Could have been deleted
|
||||
if let Ok(mut message) = Reference::from_unchecked(&system_message_id)
|
||||
.as_message(db)
|
||||
.await
|
||||
{
|
||||
if let Some(SystemMessage::CallStarted { finished_at, .. }) =
|
||||
&mut message.system
|
||||
{
|
||||
*finished_at = Some(Timestamp::now_utc());
|
||||
// if let Some(system_message_id) =
|
||||
// take_channel_call_started_system_message(channel_id).await?
|
||||
// {
|
||||
// // Could have been deleted
|
||||
// if let Ok(mut message) = Reference::from_unchecked(&system_message_id)
|
||||
// .as_message(db)
|
||||
// .await
|
||||
// {
|
||||
// if let Some(SystemMessage::CallStarted { finished_at, .. }) =
|
||||
// &mut message.system
|
||||
// {
|
||||
// *finished_at = Some(Timestamp::now_utc());
|
||||
|
||||
message
|
||||
.update(
|
||||
db,
|
||||
PartialMessage {
|
||||
system: message.system.clone(),
|
||||
..Default::default()
|
||||
},
|
||||
Vec::new(),
|
||||
)
|
||||
.await?;
|
||||
} else {
|
||||
log::error!("Broken State: Call started message ID ({}) does not contain a CallStarted system message.", &message.id)
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
// message
|
||||
// .update(
|
||||
// db,
|
||||
// PartialMessage {
|
||||
// system: message.system.clone(),
|
||||
// ..Default::default()
|
||||
// },
|
||||
// Vec::new(),
|
||||
// )
|
||||
// .await?;
|
||||
// } else {
|
||||
// log::error!("Broken State: Call started message ID ({}) does not contain a CallStarted system message.", &message.id)
|
||||
// }
|
||||
// };
|
||||
// };
|
||||
// }
|
||||
}
|
||||
// Audio/video track was started/stopped/unmuted/muted
|
||||
"track_published" | "track_unpublished" | "track_unmuted" | "track_muted" => {
|
||||
|
||||
@@ -4,7 +4,7 @@ use revolt_database::{
|
||||
delete_voice_channel, is_in_voice_channel, remove_user_from_voice_channel,
|
||||
UserVoiceChannel, VoiceClient,
|
||||
},
|
||||
AuditLogEntryAction, Channel, Database, PartialChannel, User, AMQP,
|
||||
Channel, Database, PartialChannel, User, AMQP,
|
||||
};
|
||||
use revolt_models::v0;
|
||||
use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
|
||||
@@ -12,8 +12,6 @@ use revolt_result::{create_error, Result};
|
||||
use rocket::State;
|
||||
use rocket_empty::EmptyResponse;
|
||||
|
||||
use crate::util::audit_log_reason::AuditLogReason;
|
||||
|
||||
/// # Close Channel
|
||||
///
|
||||
/// Deletes a server channel, leaves a group or closes a group.
|
||||
@@ -24,7 +22,6 @@ pub async fn delete(
|
||||
voice_client: &State<VoiceClient>,
|
||||
amqp: &State<AMQP>,
|
||||
user: User,
|
||||
reason: AuditLogReason,
|
||||
target: Reference<'_>,
|
||||
options: v0::OptionsChannelDelete,
|
||||
) -> Result<EmptyResponse> {
|
||||
@@ -66,17 +63,10 @@ pub async fn delete(
|
||||
remove_user_from_voice_channel(voice_client, &user_voice_channel, &user.id).await?;
|
||||
};
|
||||
}
|
||||
Channel::TextChannel { name, server, .. } => {
|
||||
Channel::TextChannel { .. } => {
|
||||
permissions.throw_if_lacking_channel_permission(ChannelPermission::ManageChannel)?;
|
||||
channel.delete(db).await?;
|
||||
|
||||
AuditLogEntryAction::ChannelDelete {
|
||||
channel: channel.id().to_string(),
|
||||
name: name.clone(),
|
||||
}
|
||||
.insert(db, server.clone(), reason, user.id, None)
|
||||
.await;
|
||||
|
||||
delete_voice_channel(voice_client, &UserVoiceChannel::from_channel(&channel)).await?;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
use revolt_database::{
|
||||
util::{permissions::DatabasePermissionQuery, reference::Reference},
|
||||
voice::{delete_voice_channel, UserVoiceChannel, VoiceClient},
|
||||
AuditLogEntryAction, Channel, Database, FieldsChannel, File, PartialChannel, SystemMessage,
|
||||
User, AMQP,
|
||||
Channel, Database, File, PartialChannel, SystemMessage, User, AMQP,
|
||||
};
|
||||
use revolt_models::v0;
|
||||
use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
|
||||
@@ -10,8 +9,6 @@ use revolt_result::{create_error, Result};
|
||||
use rocket::{serde::json::Json, State};
|
||||
use validator::Validate;
|
||||
|
||||
use crate::util::audit_log_reason::AuditLogReason;
|
||||
|
||||
/// # Edit Channel
|
||||
///
|
||||
/// Edit a channel object by its id.
|
||||
@@ -22,7 +19,6 @@ pub async fn edit(
|
||||
voice_client: &State<VoiceClient>,
|
||||
amqp: &State<AMQP>,
|
||||
user: User,
|
||||
reason: AuditLogReason,
|
||||
target: Reference<'_>,
|
||||
data: Json<v0::DataEditChannel>,
|
||||
) -> Result<Json<v0::Channel>> {
|
||||
@@ -95,8 +91,6 @@ pub async fn edit(
|
||||
.ok();
|
||||
}
|
||||
|
||||
let before_channel = channel.clone();
|
||||
|
||||
match &mut channel {
|
||||
Channel::Group {
|
||||
id,
|
||||
@@ -227,9 +221,6 @@ pub async fn edit(
|
||||
v0::FieldsChannel::Voice => {
|
||||
voice.take();
|
||||
}
|
||||
v0::FieldsChannel::Slowmode => {
|
||||
slowmode.take();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@@ -267,33 +258,17 @@ pub async fn edit(
|
||||
_ => return Err(create_error!(InvalidOperation)),
|
||||
};
|
||||
|
||||
let remove = data
|
||||
.remove
|
||||
.into_iter()
|
||||
.map(|f| f.into())
|
||||
.collect::<Vec<FieldsChannel>>();
|
||||
|
||||
let before = if before_channel.server().is_some() {
|
||||
Some(before_channel.generate_diff(&partial, &remove))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
channel.update(db, partial.clone(), remove).await?;
|
||||
channel
|
||||
.update(
|
||||
db,
|
||||
partial,
|
||||
data.remove.into_iter().map(|f| f.into()).collect(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
if channel.voice().is_none() {
|
||||
delete_voice_channel(voice_client, &UserVoiceChannel::from_channel(&channel)).await?;
|
||||
}
|
||||
|
||||
if let Some(before) = before {
|
||||
AuditLogEntryAction::ChannelEdit {
|
||||
channel: channel.id().to_string(),
|
||||
before,
|
||||
after: partial,
|
||||
}
|
||||
.insert(db, channel.server().unwrap().to_string(), reason, user.id, None)
|
||||
.await;
|
||||
};
|
||||
|
||||
Ok(Json(channel.into()))
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
use revolt_database::{
|
||||
util::reference::Reference,
|
||||
voice::{is_in_voice_channel, remove_user_from_voice_channel, UserVoiceChannel, VoiceClient},
|
||||
Channel, Database, User, AMQP,
|
||||
AMQP, Channel, Database, User, util::reference::Reference, voice::{UserVoiceChannel, VoiceClient, is_in_voice_channel, remove_user_from_voice_channel}
|
||||
};
|
||||
use revolt_permissions::ChannelPermission;
|
||||
use revolt_result::{create_error, Result};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use revolt_database::{
|
||||
util::{permissions::DatabasePermissionQuery, reference::Reference},
|
||||
AuditLogEntryAction, Database, Invite, User,
|
||||
Database, Invite, User,
|
||||
};
|
||||
use revolt_models::v0;
|
||||
use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
|
||||
@@ -8,8 +8,6 @@ use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
|
||||
use revolt_result::{create_error, Result};
|
||||
use rocket::{serde::json::Json, State};
|
||||
|
||||
use crate::util::audit_log_reason::AuditLogReason;
|
||||
|
||||
/// # Create Invite
|
||||
///
|
||||
/// Creates an invite to this channel.
|
||||
@@ -20,7 +18,6 @@ use crate::util::audit_log_reason::AuditLogReason;
|
||||
pub async fn create_invite(
|
||||
db: &State<Database>,
|
||||
user: User,
|
||||
reason: AuditLogReason,
|
||||
target: Reference<'_>,
|
||||
) -> Result<Json<v0::Invite>> {
|
||||
if user.bot.is_some() {
|
||||
@@ -33,16 +30,8 @@ pub async fn create_invite(
|
||||
.await
|
||||
.throw_if_lacking_channel_permission(ChannelPermission::InviteOthers)?;
|
||||
|
||||
let invite = Invite::create_channel_invite(db, &user, &channel).await?;
|
||||
|
||||
if let Some(server_id) = channel.server() {
|
||||
AuditLogEntryAction::InviteCreate {
|
||||
invite: invite.code().to_string(),
|
||||
channel: channel.id().to_string(),
|
||||
}
|
||||
.insert(db, server_id.to_string(), reason, user.id, None)
|
||||
.await;
|
||||
}
|
||||
|
||||
Ok(Json(invite.into()))
|
||||
Invite::create_channel_invite(db, &user, &channel)
|
||||
.await
|
||||
.map(|invite| invite.into())
|
||||
.map(Json)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::time::Duration;
|
||||
|
||||
use revolt_database::{
|
||||
util::{permissions::DatabasePermissionQuery, reference::Reference},
|
||||
AuditLogEntryAction, Database, Message, User,
|
||||
Database, Message, User,
|
||||
};
|
||||
use revolt_models::v0;
|
||||
use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
|
||||
@@ -11,8 +11,6 @@ use rocket::{serde::json::Json, State};
|
||||
use rocket_empty::EmptyResponse;
|
||||
use validator::Validate;
|
||||
|
||||
use crate::util::audit_log_reason::AuditLogReason;
|
||||
|
||||
/// # Bulk Delete Messages
|
||||
///
|
||||
/// Delete multiple messages you've sent or one you have permission to delete.
|
||||
@@ -25,7 +23,6 @@ use crate::util::audit_log_reason::AuditLogReason;
|
||||
pub async fn bulk_delete_messages(
|
||||
db: &State<Database>,
|
||||
user: User,
|
||||
reason: AuditLogReason,
|
||||
target: Reference<'_>,
|
||||
options: Json<v0::OptionsBulkDelete>,
|
||||
) -> Result<EmptyResponse> {
|
||||
@@ -54,16 +51,7 @@ pub async fn bulk_delete_messages(
|
||||
.await
|
||||
.throw_if_lacking_channel_permission(ChannelPermission::ManageMessages)?;
|
||||
|
||||
Message::bulk_delete(db, target.id, options.ids.clone()).await?;
|
||||
|
||||
if let Some(server) = channel.server() {
|
||||
AuditLogEntryAction::MessageBulkDelete {
|
||||
channel: channel.id().to_string(),
|
||||
count: options.ids.len(),
|
||||
}
|
||||
.insert(db, server.to_string(), reason, user.id, None)
|
||||
.await;
|
||||
};
|
||||
|
||||
Ok(EmptyResponse)
|
||||
Message::bulk_delete(db, target.id, options.ids)
|
||||
.await
|
||||
.map(|_| EmptyResponse)
|
||||
}
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
use revolt_database::{
|
||||
util::{permissions::DatabasePermissionQuery, reference::Reference},
|
||||
AuditLogEntryAction, Database, User,
|
||||
Database, User,
|
||||
};
|
||||
use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
|
||||
use revolt_result::Result;
|
||||
use rocket::State;
|
||||
use rocket_empty::EmptyResponse;
|
||||
|
||||
use crate::util::audit_log_reason::AuditLogReason;
|
||||
|
||||
/// # Delete Message
|
||||
///
|
||||
/// Delete a message you've sent or one you have permission to delete.
|
||||
@@ -17,40 +15,18 @@ use crate::util::audit_log_reason::AuditLogReason;
|
||||
pub async fn delete(
|
||||
db: &State<Database>,
|
||||
user: User,
|
||||
reason: AuditLogReason,
|
||||
target: Reference<'_>,
|
||||
msg: Reference<'_>,
|
||||
) -> Result<EmptyResponse> {
|
||||
let message = msg.as_message_in_channel(db, target.id).await?;
|
||||
|
||||
let channel = if message.author != user.id {
|
||||
if message.author != user.id {
|
||||
let channel = target.as_channel(db).await?;
|
||||
let mut query = DatabasePermissionQuery::new(db, &user).channel(&channel);
|
||||
calculate_channel_permissions(&mut query)
|
||||
.await
|
||||
.throw_if_lacking_channel_permission(ChannelPermission::ManageMessages)?;
|
||||
}
|
||||
|
||||
Some(channel)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
message.delete(db).await?;
|
||||
|
||||
if let Some(server) = channel.and_then(|c| c.server().map(|s| s.to_string())) {
|
||||
AuditLogEntryAction::MessageDelete {
|
||||
author: message.author.clone(),
|
||||
channel: message.channel.clone(),
|
||||
}
|
||||
.insert(
|
||||
db,
|
||||
server.to_string(),
|
||||
reason,
|
||||
user.id.clone(),
|
||||
Some(message.author),
|
||||
)
|
||||
.await;
|
||||
};
|
||||
|
||||
Ok(EmptyResponse)
|
||||
message.delete(db).await.map(|_| EmptyResponse)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use revolt_database::{
|
||||
util::{permissions::DatabasePermissionQuery, reference::Reference},
|
||||
AuditLogEntryAction, Channel, Database, PartialMessage, SystemMessage, User, AMQP,
|
||||
Channel, Database, PartialMessage, SystemMessage, User, AMQP,
|
||||
};
|
||||
use revolt_models::v0::MessageAuthor;
|
||||
use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
|
||||
@@ -8,8 +8,6 @@ use revolt_result::{create_error, Result};
|
||||
use rocket::State;
|
||||
use rocket_empty::EmptyResponse;
|
||||
|
||||
use crate::util::audit_log_reason::AuditLogReason;
|
||||
|
||||
/// # Pins a message
|
||||
///
|
||||
/// Pins a message by its id.
|
||||
@@ -19,7 +17,6 @@ pub async fn message_pin(
|
||||
db: &State<Database>,
|
||||
amqp: &State<AMQP>,
|
||||
user: User,
|
||||
reason: AuditLogReason,
|
||||
target: Reference<'_>,
|
||||
msg: Reference<'_>,
|
||||
) -> Result<EmptyResponse> {
|
||||
@@ -68,22 +65,6 @@ pub async fn message_pin(
|
||||
)
|
||||
.await?;
|
||||
|
||||
if let Some(server_id) = channel.server() {
|
||||
AuditLogEntryAction::MessagePin {
|
||||
message: message.id.clone(),
|
||||
author: message.author.clone(),
|
||||
channel: message.channel.clone(),
|
||||
}
|
||||
.insert(
|
||||
db,
|
||||
server_id.to_string(),
|
||||
reason,
|
||||
user.id,
|
||||
Some(message.author),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
Ok(EmptyResponse)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use revolt_database::{
|
||||
util::{permissions::DatabasePermissionQuery, reference::Reference},
|
||||
AuditLogEntryAction, Channel, Database, FieldsMessage, PartialMessage, SystemMessage, User,
|
||||
AMQP,
|
||||
Channel, Database, FieldsMessage, PartialMessage, SystemMessage, User, AMQP,
|
||||
};
|
||||
use revolt_models::v0::MessageAuthor;
|
||||
use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
|
||||
@@ -9,8 +8,6 @@ use revolt_result::{create_error, Result};
|
||||
use rocket::State;
|
||||
use rocket_empty::EmptyResponse;
|
||||
|
||||
use crate::util::audit_log_reason::AuditLogReason;
|
||||
|
||||
/// # Unpins a message
|
||||
///
|
||||
/// Unpins a message by its id.
|
||||
@@ -20,7 +17,6 @@ pub async fn message_unpin(
|
||||
db: &State<Database>,
|
||||
amqp: &State<AMQP>,
|
||||
user: User,
|
||||
reason: AuditLogReason,
|
||||
target: Reference<'_>,
|
||||
msg: Reference<'_>,
|
||||
) -> Result<EmptyResponse> {
|
||||
@@ -62,22 +58,6 @@ pub async fn message_unpin(
|
||||
)
|
||||
.await?;
|
||||
|
||||
if let Some(server_id) = channel.server() {
|
||||
AuditLogEntryAction::MessageUnpin {
|
||||
message: message.id.clone(),
|
||||
author: message.author.clone(),
|
||||
channel: message.channel.clone(),
|
||||
}
|
||||
.insert(
|
||||
db,
|
||||
server_id.to_string(),
|
||||
reason,
|
||||
user.id,
|
||||
Some(message.author),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
Ok(EmptyResponse)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
use revolt_database::{
|
||||
util::{permissions::DatabasePermissionQuery, reference::Reference},
|
||||
voice::{sync_voice_permissions, VoiceClient},
|
||||
AuditLogEntryAction, Database, User,
|
||||
util::{permissions::DatabasePermissionQuery, reference::Reference}, voice::{sync_voice_permissions, VoiceClient}, Database, User
|
||||
};
|
||||
use revolt_models::v0;
|
||||
use revolt_permissions::{ChannelPermission, Override, OverrideField, PermissionQuery, calculate_channel_permissions};
|
||||
use revolt_permissions::{calculate_channel_permissions, ChannelPermission, Override, PermissionQuery};
|
||||
use revolt_result::{create_error, Result};
|
||||
use rocket::{serde::json::Json, State};
|
||||
|
||||
use crate::util::audit_log_reason::AuditLogReason;
|
||||
|
||||
/// # Set Role Permission
|
||||
///
|
||||
/// Sets permissions for the specified role in this channel.
|
||||
@@ -21,15 +17,13 @@ pub async fn set_role_permissions(
|
||||
db: &State<Database>,
|
||||
voice_client: &State<VoiceClient>,
|
||||
user: User,
|
||||
reason: AuditLogReason,
|
||||
target: Reference<'_>,
|
||||
role_id: String,
|
||||
data: Json<v0::DataSetRolePermissions>,
|
||||
) -> Result<Json<v0::Channel>> {
|
||||
let channel = target.as_channel(db).await?;
|
||||
let mut query = DatabasePermissionQuery::new(db, &user).channel(&channel);
|
||||
let permissions: revolt_permissions::PermissionValue =
|
||||
calculate_channel_permissions(&mut query).await;
|
||||
let permissions: revolt_permissions::PermissionValue = calculate_channel_permissions(&mut query).await;
|
||||
|
||||
query.set_server_from_channel().await;
|
||||
|
||||
@@ -49,23 +43,12 @@ pub async fn set_role_permissions(
|
||||
.await?;
|
||||
|
||||
let mut new_channel = channel.clone();
|
||||
let override_field: OverrideField = data.permissions.clone().into();
|
||||
let server_id = server.id.clone();
|
||||
|
||||
new_channel
|
||||
.set_role_permission(db, &role_id, data.permissions.clone().into())
|
||||
.await?;
|
||||
|
||||
sync_voice_permissions(db, voice_client, &new_channel, Some(server), Some(&role_id))
|
||||
.await?;
|
||||
|
||||
AuditLogEntryAction::ChannelRolePermissionsEdit {
|
||||
channel: new_channel.id().to_string(),
|
||||
role: role_id,
|
||||
permissions: override_field,
|
||||
}
|
||||
.insert(db, server_id, reason, user.id, None)
|
||||
.await;
|
||||
sync_voice_permissions(db, voice_client, &new_channel, Some(server), Some(&role_id)).await?;
|
||||
|
||||
Ok(Json(new_channel.into()))
|
||||
} else {
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
use revolt_database::{
|
||||
util::{permissions::DatabasePermissionQuery, reference::Reference},
|
||||
voice::{sync_voice_permissions, VoiceClient},
|
||||
AuditLogEntryAction, Channel, Database, PartialChannel, User,
|
||||
util::{permissions::DatabasePermissionQuery, reference::Reference}, voice::{sync_voice_permissions, VoiceClient}, Channel, Database, PartialChannel, User
|
||||
};
|
||||
use revolt_models::v0::{self, DataDefaultChannelPermissions};
|
||||
use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
|
||||
use revolt_result::{create_error, Result};
|
||||
use rocket::{serde::json::Json, State};
|
||||
|
||||
use crate::util::audit_log_reason::AuditLogReason;
|
||||
|
||||
/// # Set Default Permission
|
||||
///
|
||||
/// Sets permissions for the default role in this channel.
|
||||
@@ -21,7 +17,6 @@ pub async fn set_default_channel_permissions(
|
||||
db: &State<Database>,
|
||||
voice_client: &State<VoiceClient>,
|
||||
user: User,
|
||||
reason: AuditLogReason,
|
||||
target: Reference<'_>,
|
||||
data: Json<v0::DataDefaultChannelPermissions>,
|
||||
) -> Result<Json<v0::Channel>> {
|
||||
@@ -51,8 +46,6 @@ pub async fn set_default_channel_permissions(
|
||||
}
|
||||
}
|
||||
Channel::TextChannel {
|
||||
id,
|
||||
server,
|
||||
default_permissions,
|
||||
..
|
||||
} => {
|
||||
@@ -61,25 +54,16 @@ pub async fn set_default_channel_permissions(
|
||||
.throw_permission_override(default_permissions.map(|x| x.into()), &field)
|
||||
.await?;
|
||||
|
||||
let partial = PartialChannel {
|
||||
default_permissions: Some(field.into()),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let id = id.clone();
|
||||
let server = server.clone();
|
||||
|
||||
let before = channel.generate_diff(&partial, &[]);
|
||||
|
||||
channel.update(db, partial.clone(), vec![]).await?;
|
||||
|
||||
AuditLogEntryAction::ChannelEdit {
|
||||
channel: id,
|
||||
before,
|
||||
after: partial,
|
||||
}
|
||||
.insert(db, server, reason, user.id, None)
|
||||
.await;
|
||||
channel
|
||||
.update(
|
||||
db,
|
||||
PartialChannel {
|
||||
default_permissions: Some(field.into()),
|
||||
..Default::default()
|
||||
},
|
||||
vec![],
|
||||
)
|
||||
.await?;
|
||||
} else {
|
||||
return Err(create_error!(InvalidOperation));
|
||||
}
|
||||
@@ -89,7 +73,7 @@ pub async fn set_default_channel_permissions(
|
||||
|
||||
let server = match channel.server() {
|
||||
Some(server_id) => Some(Reference::from_unchecked(server_id).as_server(db).await?),
|
||||
None => None,
|
||||
None => None
|
||||
};
|
||||
|
||||
sync_voice_permissions(db, voice_client, &channel, server.as_ref(), None).await?;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use revolt_database::{
|
||||
util::{permissions::DatabasePermissionQuery, reference::Reference},
|
||||
AuditLogEntryAction, Channel, Database, File, User, Webhook,
|
||||
Channel, Database, File, User, Webhook,
|
||||
};
|
||||
use revolt_models::v0;
|
||||
use revolt_permissions::{
|
||||
@@ -11,8 +11,6 @@ use rocket::{serde::json::Json, State};
|
||||
use ulid::Ulid;
|
||||
use validator::Validate;
|
||||
|
||||
use crate::util::audit_log_reason::AuditLogReason;
|
||||
|
||||
/// # Creates a webhook
|
||||
///
|
||||
/// Creates a webhook which 3rd party platforms can use to send messages
|
||||
@@ -21,7 +19,6 @@ use crate::util::audit_log_reason::AuditLogReason;
|
||||
pub async fn create_webhook(
|
||||
db: &State<Database>,
|
||||
user: User,
|
||||
reason: AuditLogReason,
|
||||
channel_id: Reference<'_>,
|
||||
data: Json<v0::CreateWebhookBody>,
|
||||
) -> Result<Json<v0::Webhook>> {
|
||||
@@ -54,7 +51,7 @@ pub async fn create_webhook(
|
||||
id: webhook_id,
|
||||
name: data.name,
|
||||
avatar,
|
||||
creator_id: user.id.clone(),
|
||||
creator_id: user.id,
|
||||
channel_id: channel.id().to_string(),
|
||||
permissions: *DEFAULT_WEBHOOK_PERMISSIONS,
|
||||
token: Some(nanoid::nanoid!(64)),
|
||||
@@ -62,15 +59,5 @@ pub async fn create_webhook(
|
||||
|
||||
webhook.create(db).await?;
|
||||
|
||||
if let Some(server_id) = channel.server() {
|
||||
AuditLogEntryAction::WebhookCreate {
|
||||
webhook: webhook.id.clone(),
|
||||
name: webhook.name.clone(),
|
||||
channel: webhook.channel_id.clone(),
|
||||
}
|
||||
.insert(db, server_id.to_string(), reason, user.id, None)
|
||||
.await;
|
||||
};
|
||||
|
||||
Ok(Json(webhook.into()))
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use revolt_config::config;
|
||||
use revolt_database::{AuditLogEntryAction, Database, Emoji, File, User, util::permissions::DatabasePermissionQuery};
|
||||
use revolt_database::{util::permissions::DatabasePermissionQuery, Database, Emoji, File, User};
|
||||
use revolt_models::v0;
|
||||
use revolt_permissions::{calculate_server_permissions, ChannelPermission};
|
||||
use revolt_result::{create_error, Result};
|
||||
@@ -7,8 +7,6 @@ use validator::Validate;
|
||||
|
||||
use rocket::{serde::json::Json, State};
|
||||
|
||||
use crate::util::audit_log_reason::AuditLogReason;
|
||||
|
||||
/// # Create New Emoji
|
||||
///
|
||||
/// Create an emoji by its Autumn upload id.
|
||||
@@ -17,7 +15,6 @@ use crate::util::audit_log_reason::AuditLogReason;
|
||||
pub async fn create_emoji(
|
||||
db: &State<Database>,
|
||||
user: User,
|
||||
reason: AuditLogReason,
|
||||
emoji_id: String,
|
||||
data: Json<v0::DataCreateEmoji>,
|
||||
) -> Result<Json<v0::Emoji>> {
|
||||
@@ -58,8 +55,8 @@ pub async fn create_emoji(
|
||||
// Create the emoji object
|
||||
let emoji = Emoji {
|
||||
id: emoji_id,
|
||||
parent: data.parent.clone().into(),
|
||||
creator_id: user.id.clone(),
|
||||
parent: data.parent.into(),
|
||||
creator_id: user.id,
|
||||
name: data.name,
|
||||
animated: "image/gif" == &attachment.content_type,
|
||||
nsfw: data.nsfw,
|
||||
@@ -67,12 +64,5 @@ pub async fn create_emoji(
|
||||
|
||||
// Save emoji
|
||||
emoji.create(db).await?;
|
||||
|
||||
if let v0::EmojiParent::Server { id: server_id } = data.parent {
|
||||
AuditLogEntryAction::EmojiCreate { emoji: emoji.id.clone(), name: emoji.name.clone() }
|
||||
.insert(db, server_id, reason, user.id, None)
|
||||
.await;
|
||||
}
|
||||
|
||||
Ok(Json(emoji.into()))
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use revolt_database::{
|
||||
util::{permissions::DatabasePermissionQuery, reference::Reference},
|
||||
AuditLogEntryAction, Database, EmojiParent, User,
|
||||
Database, EmojiParent, User,
|
||||
};
|
||||
use revolt_permissions::{calculate_server_permissions, ChannelPermission};
|
||||
use revolt_result::Result;
|
||||
@@ -8,8 +8,6 @@ use revolt_result::Result;
|
||||
use rocket::State;
|
||||
use rocket_empty::EmptyResponse;
|
||||
|
||||
use crate::util::audit_log_reason::AuditLogReason;
|
||||
|
||||
/// # Delete Emoji
|
||||
///
|
||||
/// Delete an emoji by its id.
|
||||
@@ -18,7 +16,6 @@ use crate::util::audit_log_reason::AuditLogReason;
|
||||
pub async fn delete_emoji(
|
||||
db: &State<Database>,
|
||||
user: User,
|
||||
reason: AuditLogReason,
|
||||
emoji_id: Reference<'_>,
|
||||
) -> Result<EmptyResponse> {
|
||||
// Fetch the emoji
|
||||
@@ -42,16 +39,5 @@ pub async fn delete_emoji(
|
||||
}
|
||||
|
||||
// Delete the emoji
|
||||
emoji.delete(db).await?;
|
||||
|
||||
if let EmojiParent::Server { id: server_id } = emoji.parent {
|
||||
AuditLogEntryAction::EmojiDelete {
|
||||
emoji: emoji.id,
|
||||
name: emoji.name,
|
||||
}
|
||||
.insert(db, server_id, reason, user.id, Some(emoji.creator_id))
|
||||
.await;
|
||||
};
|
||||
|
||||
Ok(EmptyResponse)
|
||||
emoji.delete(db).await.map(|_| EmptyResponse)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use revolt_database::{
|
||||
util::{permissions::DatabasePermissionQuery, reference::Reference},
|
||||
AuditLogEntryAction, Database, EmojiParent, PartialEmoji, User,
|
||||
Database, EmojiParent, PartialEmoji, User,
|
||||
};
|
||||
use revolt_models::v0;
|
||||
use revolt_permissions::{calculate_server_permissions, ChannelPermission};
|
||||
@@ -8,8 +8,6 @@ use revolt_result::{create_error, Result};
|
||||
use rocket::{serde::json::Json, State};
|
||||
use validator::Validate;
|
||||
|
||||
use crate::util::audit_log_reason::AuditLogReason;
|
||||
|
||||
/// # Edit Emoji
|
||||
///
|
||||
/// Edit an emoji by its id.
|
||||
@@ -18,7 +16,6 @@ use crate::util::audit_log_reason::AuditLogReason;
|
||||
pub async fn edit_emoji(
|
||||
db: &State<Database>,
|
||||
user: User,
|
||||
reason: AuditLogReason,
|
||||
emoji_id: Reference<'_>,
|
||||
data: Json<v0::DataEditEmoji>,
|
||||
) -> Result<Json<v0::Emoji>> {
|
||||
@@ -47,24 +44,8 @@ pub async fn edit_emoji(
|
||||
return Ok(Json(emoji.into()));
|
||||
}
|
||||
|
||||
let partial = PartialEmoji {
|
||||
name: data.name,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let before = emoji.generate_diff(&partial);
|
||||
|
||||
emoji.update(db, partial.clone()).await?;
|
||||
|
||||
if let EmojiParent::Server { id: server_id } = emoji.parent.clone() {
|
||||
AuditLogEntryAction::EmojiUpdate {
|
||||
emoji: emoji.id.clone(),
|
||||
before,
|
||||
after: partial,
|
||||
}
|
||||
.insert(db, server_id, reason, user.id, Some(emoji.creator_id.clone()))
|
||||
.await;
|
||||
};
|
||||
let partial = PartialEmoji { name: data.name };
|
||||
emoji.update(db, partial).await?;
|
||||
|
||||
Ok(Json(emoji.into()))
|
||||
}
|
||||
|
||||
@@ -1,56 +1,35 @@
|
||||
use revolt_database::{
|
||||
util::{permissions::DatabasePermissionQuery, reference::Reference},
|
||||
AuditLogEntryAction, Database, Invite, User,
|
||||
Database, Invite, User,
|
||||
};
|
||||
use revolt_permissions::{calculate_server_permissions, ChannelPermission};
|
||||
use revolt_result::Result;
|
||||
use rocket::State;
|
||||
use rocket_empty::EmptyResponse;
|
||||
|
||||
use crate::util::audit_log_reason::AuditLogReason;
|
||||
|
||||
/// # Delete Invite
|
||||
///
|
||||
/// Delete an invite by its id.
|
||||
#[openapi(tag = "Invites")]
|
||||
#[delete("/<target>")]
|
||||
pub async fn delete(
|
||||
db: &State<Database>,
|
||||
user: User,
|
||||
reason: AuditLogReason,
|
||||
target: Reference<'_>,
|
||||
) -> Result<EmptyResponse> {
|
||||
pub async fn delete(db: &State<Database>, user: User, target: Reference<'_>) -> Result<EmptyResponse> {
|
||||
let invite = target.as_invite(db).await?;
|
||||
|
||||
if user.id == invite.creator() {
|
||||
db.delete_invite(invite.code()).await?;
|
||||
db.delete_invite(invite.code()).await
|
||||
} else {
|
||||
match invite {
|
||||
Invite::Server {
|
||||
code,
|
||||
server,
|
||||
channel,
|
||||
creator,
|
||||
..
|
||||
} => {
|
||||
Invite::Server { code, server, .. } => {
|
||||
let server = db.fetch_server(&server).await?;
|
||||
let mut query = DatabasePermissionQuery::new(db, &user).server(&server);
|
||||
calculate_server_permissions(&mut query)
|
||||
.await
|
||||
.throw_if_lacking_channel_permission(ChannelPermission::ManageServer)?;
|
||||
|
||||
db.delete_invite(&code).await?;
|
||||
|
||||
AuditLogEntryAction::InviteDelete {
|
||||
invite: code,
|
||||
channel,
|
||||
}
|
||||
.insert(db, server.id, reason, user.id, Some(creator))
|
||||
.await;
|
||||
db.delete_invite(&code).await
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(EmptyResponse)
|
||||
.map(|_| EmptyResponse)
|
||||
}
|
||||
|
||||
@@ -1,227 +0,0 @@
|
||||
use revolt_database::{
|
||||
util::{permissions::DatabasePermissionQuery, reference::Reference},
|
||||
AuditLogEntry, AuditLogQuery, Database, User,
|
||||
};
|
||||
use revolt_models::v0;
|
||||
use revolt_permissions::{calculate_server_permissions, ChannelPermission};
|
||||
use revolt_result::{create_error, Result};
|
||||
use rocket::{serde::json::Json, State};
|
||||
use validator::Validate;
|
||||
|
||||
/// # Audit Log Query
|
||||
///
|
||||
/// Queries a server's audit logs.
|
||||
#[openapi(tag = "Audit Logs")]
|
||||
#[get("/<target>/audit_logs?<options..>")]
|
||||
pub async fn query(
|
||||
db: &State<Database>,
|
||||
user: User,
|
||||
target: Reference<'_>,
|
||||
options: v0::OptionsAuditLogQuery,
|
||||
) -> Result<Json<v0::AuditLogQueryResponse>> {
|
||||
options.validate().map_err(|error| {
|
||||
create_error!(FailedValidation {
|
||||
error: error.to_string()
|
||||
})
|
||||
})?;
|
||||
|
||||
let server = target.as_server(db).await?;
|
||||
|
||||
let mut query = DatabasePermissionQuery::new(db, &user).server(&server);
|
||||
calculate_server_permissions(&mut query)
|
||||
.await
|
||||
.throw_if_lacking_channel_permission(ChannelPermission::ViewAuditLogs)?;
|
||||
|
||||
let v0::OptionsAuditLogQuery {
|
||||
user: user_filter,
|
||||
target,
|
||||
r#type,
|
||||
before,
|
||||
after,
|
||||
limit,
|
||||
} = options;
|
||||
|
||||
let audit_logs = db
|
||||
.get_server_audit_logs(
|
||||
&server.id,
|
||||
AuditLogQuery {
|
||||
user: user_filter,
|
||||
target,
|
||||
r#type,
|
||||
before,
|
||||
after,
|
||||
limit: limit.unwrap_or(50),
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
let (users, members) = AuditLogEntry::with_users(db, &server.id, &user, &audit_logs).await?;
|
||||
|
||||
Ok(Json(v0::AuditLogQueryResponse {
|
||||
audit_logs: audit_logs.into_iter().map(Into::into).collect(),
|
||||
users,
|
||||
members,
|
||||
}))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use revolt_database::{Member, Server};
|
||||
use revolt_models::v0;
|
||||
use rocket::http::{Header, Status};
|
||||
|
||||
use crate::util::test::TestHarness;
|
||||
|
||||
#[rocket::async_test]
|
||||
async fn audit_log_query() {
|
||||
let harness = TestHarness::new().await;
|
||||
let (_, session, user) = harness.new_user().await;
|
||||
let (server, channels) = Server::create(
|
||||
&harness.db,
|
||||
v0::DataCreateServer {
|
||||
name: "Test Server".to_string(),
|
||||
..Default::default()
|
||||
},
|
||||
&user,
|
||||
true,
|
||||
)
|
||||
.await
|
||||
.expect("Failed to create test server.");
|
||||
Member::create(&harness.db, &server, &user, None).await.unwrap();
|
||||
|
||||
let channel = &channels[0];
|
||||
|
||||
let status = harness
|
||||
.client
|
||||
.patch(format!("/channels/{}", channel.id()))
|
||||
.header(Header::new("X-Audit-Log-Reason", "Test Reason 1"))
|
||||
.header(Header::new("x-session-token", session.token.clone()))
|
||||
.json(&v0::DataEditChannel {
|
||||
description: Some("General chat channel.".to_string()),
|
||||
name: None,
|
||||
owner: None,
|
||||
icon: None,
|
||||
nsfw: None,
|
||||
archived: None,
|
||||
voice: None,
|
||||
slowmode: None,
|
||||
remove: Vec::new(),
|
||||
})
|
||||
.dispatch()
|
||||
.await
|
||||
.status();
|
||||
|
||||
assert_eq!(status, Status::Ok);
|
||||
|
||||
let status = harness
|
||||
.client
|
||||
.patch(format!("/channels/{}", channel.id()))
|
||||
.header(Header::new("X-Audit-Log-Reason", "Test Reason 2"))
|
||||
.header(Header::new("x-session-token", session.token.clone()))
|
||||
.json(&v0::DataEditChannel {
|
||||
description: Some("New description.".to_string()),
|
||||
name: None,
|
||||
owner: None,
|
||||
icon: None,
|
||||
nsfw: None,
|
||||
archived: None,
|
||||
voice: None,
|
||||
slowmode: None,
|
||||
remove: Vec::new(),
|
||||
})
|
||||
.dispatch()
|
||||
.await
|
||||
.status();
|
||||
|
||||
assert_eq!(status, Status::Ok);
|
||||
|
||||
let status = harness
|
||||
.client
|
||||
.delete(format!("/channels/{}", channel.id()))
|
||||
.header(Header::new("X-Audit-Log-Reason", "Test Reason 3"))
|
||||
.header(Header::new("x-session-token", session.token.clone()))
|
||||
.dispatch()
|
||||
.await
|
||||
.status();
|
||||
|
||||
assert_eq!(status, Status::NoContent);
|
||||
|
||||
let response = harness
|
||||
.client
|
||||
.get(format!(
|
||||
"/servers/{}/audit_logs?include_users=true",
|
||||
&server.id
|
||||
))
|
||||
.header(Header::new("x-session-token", session.token.clone()))
|
||||
.dispatch()
|
||||
.await
|
||||
.into_json::<v0::AuditLogQueryResponse>()
|
||||
.await
|
||||
.expect("Failed to deserialise audit_logs response");
|
||||
|
||||
let v0::AuditLogQueryResponse {
|
||||
audit_logs: entries,
|
||||
users,
|
||||
members,
|
||||
} = response;
|
||||
|
||||
assert_eq!(entries.len(), 3);
|
||||
assert_eq!(users.len(), 1);
|
||||
assert_eq!(members.len(), 1);
|
||||
|
||||
assert_eq!(&users[0].id, &user.id);
|
||||
|
||||
let entry = &entries[0];
|
||||
|
||||
assert_eq!(entry.reason.as_deref(), Some("Test Reason 3"));
|
||||
assert_eq!(&entry.server, &server.id);
|
||||
assert_eq!(&entry.user, &user.id);
|
||||
assert_eq!(
|
||||
&entry.action,
|
||||
&v0::AuditLogEntryAction::ChannelDelete {
|
||||
channel: channel.id().to_string(),
|
||||
name: "General".to_string()
|
||||
}
|
||||
);
|
||||
|
||||
let entry = &entries[1];
|
||||
|
||||
assert_eq!(entry.reason.as_deref(), Some("Test Reason 2"));
|
||||
assert_eq!(&entry.server, &server.id);
|
||||
assert_eq!(&entry.user, &user.id);
|
||||
assert_eq!(
|
||||
&entry.action,
|
||||
&v0::AuditLogEntryAction::ChannelEdit {
|
||||
channel: channel.id().to_string(),
|
||||
before: v0::PartialChannel {
|
||||
description: Some("General chat channel.".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
after: v0::PartialChannel {
|
||||
description: Some("New description.".to_string()),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
let entry = &entries[2];
|
||||
|
||||
assert_eq!(entry.reason.as_deref(), Some("Test Reason 1"));
|
||||
assert_eq!(&entry.server, &server.id);
|
||||
assert_eq!(&entry.user, &user.id);
|
||||
assert_eq!(
|
||||
&entry.action,
|
||||
&v0::AuditLogEntryAction::ChannelEdit {
|
||||
channel: channel.id().to_string(),
|
||||
before: v0::PartialChannel {
|
||||
description: None,
|
||||
..Default::default()
|
||||
},
|
||||
after: v0::PartialChannel {
|
||||
description: Some("General chat channel.".to_string()),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ use revolt_database::{
|
||||
get_user_voice_channel_in_server, remove_user_from_voice_channel, UserVoiceChannel,
|
||||
VoiceClient,
|
||||
},
|
||||
AuditLogEntryAction, Database, Message, RemovalIntention, ServerBan, User,
|
||||
Database, Message, RemovalIntention, ServerBan, User,
|
||||
};
|
||||
use revolt_models::v0;
|
||||
use std::time::{Duration, SystemTime};
|
||||
@@ -16,8 +16,6 @@ use rocket::{serde::json::Json, State};
|
||||
use ulid::Ulid;
|
||||
use validator::Validate;
|
||||
|
||||
use crate::util::audit_log_reason::AuditLogReason;
|
||||
|
||||
/// # Ban User
|
||||
///
|
||||
/// Ban a user by their id.
|
||||
@@ -27,7 +25,6 @@ pub async fn ban(
|
||||
db: &State<Database>,
|
||||
voice_client: &State<VoiceClient>,
|
||||
user: User,
|
||||
audit_log_reason: AuditLogReason,
|
||||
server: Reference<'_>,
|
||||
target: Reference<'_>,
|
||||
data: Json<v0::DataBanCreate>,
|
||||
@@ -88,20 +85,8 @@ pub async fn ban(
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
let ban = ServerBan::create(db, &server, target.id, data.reason.clone()).await?;
|
||||
|
||||
AuditLogEntryAction::BanCreate {
|
||||
user: target.id.to_string(),
|
||||
}
|
||||
.insert(
|
||||
db,
|
||||
server.id,
|
||||
audit_log_reason.0.or(data.reason),
|
||||
user.id,
|
||||
Some(target.id.to_string()),
|
||||
)
|
||||
.await;
|
||||
|
||||
Ok(Json(ban.into()))
|
||||
ServerBan::create(db, &server, target.id, data.reason)
|
||||
.await
|
||||
.map(Into::into)
|
||||
.map(Json)
|
||||
}
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
use revolt_database::{
|
||||
util::{permissions::DatabasePermissionQuery, reference::Reference},
|
||||
AuditLogEntryAction, Database, User,
|
||||
Database, User,
|
||||
};
|
||||
use revolt_permissions::{calculate_server_permissions, ChannelPermission};
|
||||
use revolt_result::Result;
|
||||
use rocket::State;
|
||||
use rocket_empty::EmptyResponse;
|
||||
|
||||
use crate::util::audit_log_reason::AuditLogReason;
|
||||
|
||||
/// # Unban user
|
||||
///
|
||||
/// Remove a user's ban.
|
||||
@@ -17,7 +15,6 @@ use crate::util::audit_log_reason::AuditLogReason;
|
||||
pub async fn unban(
|
||||
db: &State<Database>,
|
||||
user: User,
|
||||
reason: AuditLogReason,
|
||||
server: Reference<'_>,
|
||||
target: Reference<'_>,
|
||||
) -> Result<EmptyResponse> {
|
||||
@@ -28,13 +25,5 @@ pub async fn unban(
|
||||
.throw_if_lacking_channel_permission(ChannelPermission::BanMembers)?;
|
||||
|
||||
let ban = target.as_ban(db, &server.id).await?;
|
||||
db.delete_ban(&ban.id).await?;
|
||||
|
||||
AuditLogEntryAction::BanDelete {
|
||||
user: target.id.to_string(),
|
||||
}
|
||||
.insert(db, server.id, reason, user.id, Some(target.id.to_string()))
|
||||
.await;
|
||||
|
||||
Ok(EmptyResponse)
|
||||
db.delete_ban(&ban.id).await.map(|_| EmptyResponse)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use revolt_database::util::permissions::DatabasePermissionQuery;
|
||||
use revolt_database::AuditLogEntryAction;
|
||||
use revolt_database::{util::reference::Reference, Channel, Database, User};
|
||||
use revolt_models::v0;
|
||||
use revolt_permissions::{calculate_server_permissions, ChannelPermission};
|
||||
@@ -9,8 +8,6 @@ use rocket::serde::json::Json;
|
||||
use rocket::State;
|
||||
use validator::Validate;
|
||||
|
||||
use crate::util::audit_log_reason::AuditLogReason;
|
||||
|
||||
/// # Create Channel
|
||||
///
|
||||
/// Create a new Text or Voice channel.
|
||||
@@ -19,7 +16,6 @@ use crate::util::audit_log_reason::AuditLogReason;
|
||||
pub async fn create_server_channel(
|
||||
db: &State<Database>,
|
||||
user: User,
|
||||
reason: AuditLogReason,
|
||||
server: Reference<'_>,
|
||||
data: Json<v0::DataCreateServerChannel>,
|
||||
) -> Result<Json<v0::Channel>> {
|
||||
@@ -36,16 +32,8 @@ pub async fn create_server_channel(
|
||||
.await
|
||||
.throw_if_lacking_channel_permission(ChannelPermission::ManageChannel)?;
|
||||
|
||||
let channel_name = data.name.clone();
|
||||
|
||||
let channel = Channel::create_server_channel(db, &mut server, data, true).await?;
|
||||
|
||||
AuditLogEntryAction::ChannelCreate {
|
||||
channel: channel.id().to_string(),
|
||||
name: channel_name,
|
||||
}
|
||||
.insert(db, server.id, reason, user.id, None)
|
||||
.await;
|
||||
|
||||
Ok(Json(channel.into()))
|
||||
Channel::create_server_channel(db, &mut server, data, true)
|
||||
.await
|
||||
.map(|channel| channel.into())
|
||||
.map(Json)
|
||||
}
|
||||
|
||||
@@ -11,19 +11,15 @@ use revolt_database::{
|
||||
set_user_moved_from_voice, set_user_moved_to_voice, sync_user_voice_permissions,
|
||||
UserVoiceChannel, VoiceClient,
|
||||
},
|
||||
AuditLogEntryAction, Database, FieldsMember, File, PartialMember, User,
|
||||
Database, File, PartialMember, User,
|
||||
};
|
||||
use revolt_models::v0;
|
||||
use revolt_models::v0::{self, FieldsMember};
|
||||
|
||||
use revolt_permissions::{
|
||||
calculate_channel_permissions, calculate_server_permissions, ChannelPermission, UserPermission,
|
||||
};
|
||||
use revolt_permissions::{calculate_channel_permissions, calculate_server_permissions, ChannelPermission, UserPermission};
|
||||
use revolt_result::{create_error, Result};
|
||||
use rocket::{form::validate::Contains, serde::json::Json, State};
|
||||
use validator::Validate;
|
||||
|
||||
use crate::util::audit_log_reason::AuditLogReason;
|
||||
|
||||
/// # Edit Member
|
||||
///
|
||||
/// Edit a member by their id.
|
||||
@@ -33,7 +29,6 @@ pub async fn edit(
|
||||
db: &State<Database>,
|
||||
voice_client: &State<VoiceClient>,
|
||||
user: User,
|
||||
reason: AuditLogReason,
|
||||
server_id: Reference<'_>,
|
||||
member_id: Reference<'_>,
|
||||
data: Json<v0::DataMemberEdit>,
|
||||
@@ -81,7 +76,7 @@ pub async fn edit(
|
||||
} else if data.remove.contains(&v0::FieldsMember::Avatar) {
|
||||
permissions.throw_if_lacking_channel_permission(ChannelPermission::RemoveAvatars)?;
|
||||
} else {
|
||||
return Err(create_error!(InvalidOperation));
|
||||
return Err(create_error!(InvalidOperation))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,11 +106,11 @@ pub async fn edit(
|
||||
permissions.throw_if_lacking_channel_permission(ChannelPermission::DeafenMembers)?;
|
||||
}
|
||||
|
||||
if data.voice_channel.is_some() && data.remove.contains(&v0::FieldsMember::VoiceChannel) {
|
||||
if data.voice_channel.is_some() && data.remove.contains(&FieldsMember::VoiceChannel) {
|
||||
return Err(create_error!(InvalidOperation));
|
||||
}
|
||||
|
||||
if data.voice_channel.is_some() || data.remove.contains(&v0::FieldsMember::VoiceChannel) {
|
||||
if data.voice_channel.is_some() || data.remove.contains(&FieldsMember::VoiceChannel) {
|
||||
if !voice_client.is_enabled() {
|
||||
return Err(create_error!(LiveKitUnavailable));
|
||||
};
|
||||
@@ -137,8 +132,7 @@ pub async fn edit(
|
||||
Err(create_error!(UnknownChannel))?
|
||||
}
|
||||
|
||||
let channel_permissions =
|
||||
calculate_channel_permissions(&mut query.clone().channel(&channel)).await;
|
||||
let channel_permissions = calculate_channel_permissions(&mut query.clone().channel(&channel)).await;
|
||||
channel_permissions.throw_if_lacking_channel_permission(ChannelPermission::Connect)?;
|
||||
|
||||
if get_user_voice_channel_in_server(&target_user.id, &server.id)
|
||||
@@ -216,28 +210,9 @@ pub async fn edit(
|
||||
partial.avatar = Some(File::use_user_avatar(db, &avatar, &user.id, &user.id).await?);
|
||||
}
|
||||
|
||||
let remove = remove
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect::<Vec<FieldsMember>>();
|
||||
|
||||
let before = member.generate_diff(&partial, &remove);
|
||||
|
||||
member.update(db, partial.clone(), remove.clone()).await?;
|
||||
|
||||
AuditLogEntryAction::MemberEdit {
|
||||
user: member.id.user.clone(),
|
||||
before,
|
||||
after: partial,
|
||||
}
|
||||
.insert(
|
||||
db,
|
||||
server.id.clone(),
|
||||
reason,
|
||||
user.id.clone(),
|
||||
Some(member.id.user.clone()),
|
||||
)
|
||||
.await;
|
||||
member
|
||||
.update(db, partial, remove.clone().into_iter().map(Into::into).collect())
|
||||
.await?;
|
||||
|
||||
if let Some(new_voice_channel) = new_voice_channel {
|
||||
if let Some(channel) = get_user_voice_channel_in_server(&target_user.id, &server.id).await?
|
||||
@@ -289,11 +264,7 @@ pub async fn edit(
|
||||
.private(target_user.id.clone())
|
||||
.await;
|
||||
};
|
||||
} else if can_publish.is_some()
|
||||
|| can_receive.is_some()
|
||||
|| remove.contains(FieldsMember::CanPublish)
|
||||
|| remove.contains(FieldsMember::CanReceive)
|
||||
{
|
||||
} else if can_publish.is_some() || can_receive.is_some() || remove.contains(FieldsMember::CanPublish) || remove.contains(FieldsMember::CanReceive) {
|
||||
if let Some(channel) = get_user_voice_channel_in_server(&target_user.id, &server.id).await?
|
||||
{
|
||||
let node = get_channel_node(&channel).await?.unwrap();
|
||||
|
||||
@@ -4,15 +4,13 @@ use revolt_database::{
|
||||
get_user_voice_channel_in_server, remove_user_from_voice_channel, UserVoiceChannel,
|
||||
VoiceClient,
|
||||
},
|
||||
AuditLogEntryAction, Database, RemovalIntention, User,
|
||||
Database, RemovalIntention, User,
|
||||
};
|
||||
use revolt_permissions::{calculate_server_permissions, ChannelPermission};
|
||||
use revolt_result::{create_error, Result};
|
||||
use rocket::State;
|
||||
use rocket_empty::EmptyResponse;
|
||||
|
||||
use crate::util::audit_log_reason::AuditLogReason;
|
||||
|
||||
/// # Kick Member
|
||||
///
|
||||
/// Removes a member from the server.
|
||||
@@ -22,7 +20,6 @@ pub async fn kick(
|
||||
db: &State<Database>,
|
||||
voice_client: &State<VoiceClient>,
|
||||
user: User,
|
||||
reason: AuditLogReason,
|
||||
server_id: Reference<'_>,
|
||||
member_id: Reference<'_>,
|
||||
) -> Result<EmptyResponse> {
|
||||
@@ -52,18 +49,6 @@ pub async fn kick(
|
||||
.remove(db, &server, RemovalIntention::Kick, false)
|
||||
.await?;
|
||||
|
||||
AuditLogEntryAction::MemberKick {
|
||||
user: member.id.user.clone(),
|
||||
}
|
||||
.insert(
|
||||
db,
|
||||
server.id.clone(),
|
||||
reason,
|
||||
user.id,
|
||||
Some(member.id.user.clone()),
|
||||
)
|
||||
.await;
|
||||
|
||||
if let Some(channel_id) = get_user_voice_channel_in_server(member_id.id, &server.id).await? {
|
||||
remove_user_from_voice_channel(
|
||||
voice_client,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use revolt_rocket_okapi::revolt_okapi::openapi3::OpenApi;
|
||||
use rocket::Route;
|
||||
|
||||
mod audit_log_query;
|
||||
mod ban_create;
|
||||
mod ban_list;
|
||||
mod ban_remove;
|
||||
@@ -50,7 +49,6 @@ pub fn routes() -> (Vec<Route>, OpenApi) {
|
||||
permissions_set::set_role_permission,
|
||||
permissions_set_default::set_default_server_permissions,
|
||||
emoji_list::list_emoji,
|
||||
roles_edit_positions::edit_role_ranks,
|
||||
audit_log_query::query,
|
||||
roles_edit_positions::edit_role_ranks
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
use revolt_database::{
|
||||
util::{permissions::DatabasePermissionQuery, reference::Reference},
|
||||
voice::{sync_voice_permissions, VoiceClient},
|
||||
AuditLogEntryAction, Database, PartialRole, User,
|
||||
Database, User,
|
||||
};
|
||||
use revolt_models::v0;
|
||||
use revolt_permissions::{
|
||||
calculate_server_permissions, ChannelPermission, Override, OverrideField,
|
||||
};
|
||||
use revolt_permissions::{calculate_server_permissions, ChannelPermission, Override};
|
||||
use revolt_result::{create_error, Result};
|
||||
use rocket::{serde::json::Json, State};
|
||||
|
||||
use crate::util::audit_log_reason::AuditLogReason;
|
||||
|
||||
/// # Set Role Permission
|
||||
///
|
||||
/// Sets permissions for the specified role in the server.
|
||||
@@ -21,7 +17,6 @@ pub async fn set_role_permission(
|
||||
db: &State<Database>,
|
||||
voice_client: &State<VoiceClient>,
|
||||
user: User,
|
||||
reason: AuditLogReason,
|
||||
target: Reference<'_>,
|
||||
role_id: String,
|
||||
data: Json<v0::DataSetServerRolePermission>,
|
||||
@@ -47,36 +42,20 @@ pub async fn set_role_permission(
|
||||
}
|
||||
|
||||
// Ensure we have access to grant these permissions forwards
|
||||
let current_override: Override = current_value.into();
|
||||
let current_value: Override = current_value.into();
|
||||
permissions
|
||||
.throw_permission_override(current_override, &data.permissions)
|
||||
.throw_permission_override(current_value, &data.permissions)
|
||||
.await?;
|
||||
|
||||
let override_field: OverrideField = data.permissions.into();
|
||||
|
||||
server
|
||||
.set_role_permission(db, &role_id, override_field)
|
||||
.set_role_permission(db, &role_id, data.permissions.into())
|
||||
.await?;
|
||||
|
||||
AuditLogEntryAction::RoleEdit {
|
||||
role: role_id.clone(),
|
||||
before: PartialRole {
|
||||
permissions: Some(current_value),
|
||||
..Default::default()
|
||||
},
|
||||
after: PartialRole {
|
||||
permissions: Some(override_field),
|
||||
..Default::default()
|
||||
},
|
||||
}
|
||||
.insert(db, server.id.clone(), reason, user.id, None)
|
||||
.await;
|
||||
|
||||
for channel_id in &server.channels {
|
||||
let channel = Reference::from_unchecked(channel_id).as_channel(db).await?;
|
||||
|
||||
sync_voice_permissions(db, voice_client, &channel, Some(&server), Some(&role_id)).await?;
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Json(server.into()))
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
use revolt_database::{
|
||||
util::{permissions::DatabasePermissionQuery, reference::Reference},
|
||||
voice::{sync_voice_permissions, VoiceClient},
|
||||
AuditLogEntryAction, Database, PartialServer, User,
|
||||
util::{permissions::DatabasePermissionQuery, reference::Reference}, voice::{sync_voice_permissions, VoiceClient}, Database, PartialServer, User
|
||||
};
|
||||
use revolt_models::v0;
|
||||
use revolt_permissions::{
|
||||
@@ -10,8 +8,6 @@ use revolt_permissions::{
|
||||
use revolt_result::Result;
|
||||
use rocket::{serde::json::Json, State};
|
||||
|
||||
use crate::util::audit_log_reason::AuditLogReason;
|
||||
|
||||
/// # Set Default Permission
|
||||
///
|
||||
/// Sets permissions for the default role in this server.
|
||||
@@ -21,7 +17,6 @@ pub async fn set_default_server_permissions(
|
||||
db: &State<Database>,
|
||||
voice_client: &State<VoiceClient>,
|
||||
user: User,
|
||||
reason: AuditLogReason,
|
||||
target: Reference<'_>,
|
||||
data: Json<DataPermissionsValue>,
|
||||
) -> Result<Json<v0::Server>> {
|
||||
@@ -44,27 +39,22 @@ pub async fn set_default_server_permissions(
|
||||
)
|
||||
.await?;
|
||||
|
||||
let partial = PartialServer {
|
||||
default_permissions: Some(data.permissions as i64),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let before = server.generate_diff(&partial, &[]);
|
||||
|
||||
server.update(db, partial.clone(), vec![]).await?;
|
||||
|
||||
AuditLogEntryAction::ServerEdit {
|
||||
before,
|
||||
after: partial,
|
||||
}
|
||||
.insert(db, server.id.clone(), reason, user.id, None)
|
||||
.await;
|
||||
server
|
||||
.update(
|
||||
db,
|
||||
PartialServer {
|
||||
default_permissions: Some(data.permissions as i64),
|
||||
..Default::default()
|
||||
},
|
||||
vec![],
|
||||
)
|
||||
.await?;
|
||||
|
||||
for channel_id in &server.channels {
|
||||
let channel = Reference::from_unchecked(channel_id).as_channel(db).await?;
|
||||
|
||||
sync_voice_permissions(db, voice_client, &channel, Some(&server), None).await?;
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Json(server.into()))
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use revolt_config::config;
|
||||
use revolt_database::{
|
||||
util::{permissions::DatabasePermissionQuery, reference::Reference},
|
||||
AuditLogEntryAction, Database, Role, User,
|
||||
Database, Role, User,
|
||||
};
|
||||
use revolt_models::v0;
|
||||
use revolt_permissions::{calculate_server_permissions, ChannelPermission};
|
||||
@@ -9,8 +9,6 @@ use revolt_result::{create_error, Result};
|
||||
use rocket::{serde::json::Json, State};
|
||||
use validator::Validate;
|
||||
|
||||
use crate::util::audit_log_reason::AuditLogReason;
|
||||
|
||||
/// # Create Role
|
||||
///
|
||||
/// Creates a new server role.
|
||||
@@ -19,7 +17,6 @@ use crate::util::audit_log_reason::AuditLogReason;
|
||||
pub async fn create(
|
||||
db: &State<Database>,
|
||||
user: User,
|
||||
reason: AuditLogReason,
|
||||
target: Reference<'_>,
|
||||
data: Json<v0::DataCreateRole>,
|
||||
) -> Result<Json<v0::NewRoleResponse>> {
|
||||
@@ -45,13 +42,6 @@ pub async fn create(
|
||||
|
||||
let role = Role::create(db, &server, data.name).await?;
|
||||
|
||||
AuditLogEntryAction::RoleCreate {
|
||||
role: role.id.clone(),
|
||||
name: role.name.clone(),
|
||||
}
|
||||
.insert(db, server.id, reason, user.id, None)
|
||||
.await;
|
||||
|
||||
Ok(Json(v0::NewRoleResponse {
|
||||
id: role.id.clone(),
|
||||
role: role.into(),
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
use revolt_database::{
|
||||
util::{permissions::DatabasePermissionQuery, reference::Reference},
|
||||
voice::{sync_voice_permissions, VoiceClient},
|
||||
AuditLogEntryAction, Database, User,
|
||||
Database, User,
|
||||
};
|
||||
use revolt_permissions::{calculate_server_permissions, ChannelPermission};
|
||||
use revolt_result::{create_error, Result};
|
||||
use rocket::State;
|
||||
use rocket_empty::EmptyResponse;
|
||||
|
||||
use crate::util::audit_log_reason::AuditLogReason;
|
||||
|
||||
/// # Delete Role
|
||||
///
|
||||
/// Delete a server role by its id.
|
||||
@@ -18,7 +16,6 @@ use crate::util::audit_log_reason::AuditLogReason;
|
||||
pub async fn delete(
|
||||
db: &State<Database>,
|
||||
user: User,
|
||||
reason: AuditLogReason,
|
||||
target: Reference<'_>,
|
||||
role_id: String,
|
||||
voice_client: &State<VoiceClient>,
|
||||
@@ -42,13 +39,6 @@ pub async fn delete(
|
||||
|
||||
role.delete(db, &server.id).await?;
|
||||
|
||||
AuditLogEntryAction::RoleDelete {
|
||||
role: role_id.clone(),
|
||||
name: role.name,
|
||||
}
|
||||
.insert(db, server.id.clone(), reason, user.id, None)
|
||||
.await;
|
||||
|
||||
for channel_id in &server.channels {
|
||||
let channel = Reference::from_unchecked(channel_id).as_channel(db).await?;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use revolt_database::{
|
||||
util::{permissions::DatabasePermissionQuery, reference::Reference},
|
||||
voice::{sync_voice_permissions, VoiceClient},
|
||||
AuditLogEntryAction, Database, FieldsRole, PartialRole, User, File
|
||||
Database, File, PartialRole, User,
|
||||
};
|
||||
use revolt_models::v0;
|
||||
use revolt_permissions::{calculate_server_permissions, ChannelPermission};
|
||||
@@ -9,8 +9,6 @@ use revolt_result::{create_error, Result};
|
||||
use rocket::{serde::json::Json, State};
|
||||
use validator::Validate;
|
||||
|
||||
use crate::util::audit_log_reason::AuditLogReason;
|
||||
|
||||
/// # Edit Role
|
||||
///
|
||||
/// Edit a role by its id.
|
||||
@@ -20,7 +18,6 @@ pub async fn edit(
|
||||
db: &State<Database>,
|
||||
voice_client: &State<VoiceClient>,
|
||||
user: User,
|
||||
reason: AuditLogReason,
|
||||
target: Reference<'_>,
|
||||
role_id: String,
|
||||
data: Json<v0::DataEditRole>,
|
||||
@@ -74,23 +71,13 @@ pub async fn edit(
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let remove = remove
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect::<Vec<FieldsRole>>();
|
||||
|
||||
let before = role.generate_diff(&partial, &remove);
|
||||
|
||||
role.update(db, &server.id, partial.clone(), remove)
|
||||
.await?;
|
||||
|
||||
AuditLogEntryAction::RoleEdit {
|
||||
role: role_id.clone(),
|
||||
before,
|
||||
after: partial,
|
||||
}
|
||||
.insert(db, server.id.clone(), reason, user.id, None)
|
||||
.await;
|
||||
role.update(
|
||||
db,
|
||||
&server.id,
|
||||
partial,
|
||||
remove.into_iter().map(Into::into).collect(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
for channel_id in &server.channels {
|
||||
let channel = Reference::from_unchecked(channel_id).as_channel(db).await?;
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
use revolt_database::{
|
||||
util::{permissions::DatabasePermissionQuery, reference::Reference},
|
||||
voice::{sync_voice_permissions, VoiceClient},
|
||||
AuditLogEntryAction, Database, User,
|
||||
util::{permissions::DatabasePermissionQuery, reference::Reference}, voice::{sync_voice_permissions, VoiceClient}, Database, User
|
||||
};
|
||||
use revolt_models::v0;
|
||||
use revolt_permissions::{calculate_server_permissions, ChannelPermission};
|
||||
use revolt_result::{create_error, Result};
|
||||
use rocket::{serde::json::Json, State};
|
||||
|
||||
use crate::util::audit_log_reason::AuditLogReason;
|
||||
|
||||
/// # Edits server roles ranks
|
||||
///
|
||||
/// Edit's server role's ranks.
|
||||
@@ -19,7 +15,6 @@ pub async fn edit_role_ranks(
|
||||
db: &State<Database>,
|
||||
voice_client: &State<VoiceClient>,
|
||||
user: User,
|
||||
reason: AuditLogReason,
|
||||
target: Reference<'_>,
|
||||
data: Json<v0::DataEditRoleRanks>,
|
||||
) -> Result<Json<v0::Server>> {
|
||||
@@ -73,20 +68,13 @@ pub async fn edit_role_ranks(
|
||||
}
|
||||
}
|
||||
|
||||
server.set_role_ordering(db, new_order.clone()).await?;
|
||||
|
||||
AuditLogEntryAction::RolesReorder {
|
||||
before: existing_order,
|
||||
after: new_order,
|
||||
}
|
||||
.insert(db, server.id.clone(), reason, user.id, None)
|
||||
.await;
|
||||
server.set_role_ordering(db, new_order).await?;
|
||||
|
||||
for channel_id in &server.channels {
|
||||
let channel = Reference::from_unchecked(channel_id).as_channel(db).await?;
|
||||
|
||||
sync_voice_permissions(db, voice_client, &channel, Some(&server), None).await?;
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Json(server.into()))
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::collections::HashSet;
|
||||
|
||||
use revolt_database::{
|
||||
util::{permissions::DatabasePermissionQuery, reference::Reference},
|
||||
AuditLogEntryAction, Database, FieldsServer, File, PartialServer, User, ValidatedTicket
|
||||
Database, File, PartialServer, User, ValidatedTicket,
|
||||
};
|
||||
use revolt_models::v0;
|
||||
use revolt_permissions::{calculate_server_permissions, ChannelPermission};
|
||||
@@ -10,8 +10,6 @@ use revolt_result::{create_error, Result};
|
||||
use rocket::{serde::json::Json, State};
|
||||
use validator::Validate;
|
||||
|
||||
use crate::util::audit_log_reason::AuditLogReason;
|
||||
|
||||
/// # Edit Server
|
||||
///
|
||||
/// Edit a server by its id.
|
||||
@@ -20,7 +18,6 @@ use crate::util::audit_log_reason::AuditLogReason;
|
||||
pub async fn edit(
|
||||
db: &State<Database>,
|
||||
user: User,
|
||||
reason: AuditLogReason,
|
||||
target: Reference<'_>,
|
||||
data: Json<v0::DataEditServer>,
|
||||
validated_ticket: Option<ValidatedTicket>,
|
||||
@@ -179,21 +176,9 @@ pub async fn edit(
|
||||
partial.owner = Some(server.owner.clone());
|
||||
}
|
||||
|
||||
let remove = remove
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect::<Vec<FieldsServer>>();
|
||||
|
||||
let before = server.generate_diff(&partial, &remove);
|
||||
|
||||
server.update(db, partial.clone(), remove).await?;
|
||||
|
||||
AuditLogEntryAction::ServerEdit {
|
||||
before,
|
||||
after: partial,
|
||||
}
|
||||
.insert(db, server.id.clone(), reason, user.id, None)
|
||||
.await;
|
||||
server
|
||||
.update(db, partial, remove.into_iter().map(Into::into).collect())
|
||||
.await?;
|
||||
|
||||
Ok(Json(server.into()))
|
||||
}
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
use revolt_database::{
|
||||
util::{permissions::DatabasePermissionQuery, reference::Reference},
|
||||
AuditLogEntryAction, Database, User,
|
||||
Database, User,
|
||||
};
|
||||
use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
|
||||
use revolt_result::Result;
|
||||
use rocket::State;
|
||||
use rocket_empty::EmptyResponse;
|
||||
|
||||
use crate::util::audit_log_reason::AuditLogReason;
|
||||
|
||||
/// # Deletes a webhook
|
||||
///
|
||||
/// Deletes a webhook
|
||||
@@ -17,7 +15,6 @@ use crate::util::audit_log_reason::AuditLogReason;
|
||||
pub async fn webhook_delete(
|
||||
db: &State<Database>,
|
||||
user: User,
|
||||
reason: AuditLogReason,
|
||||
webhook_id: Reference<'_>,
|
||||
) -> Result<EmptyResponse> {
|
||||
let webhook = webhook_id.as_webhook(db).await?;
|
||||
@@ -28,24 +25,5 @@ pub async fn webhook_delete(
|
||||
.await
|
||||
.throw_if_lacking_channel_permission(ChannelPermission::ManageWebhooks)?;
|
||||
|
||||
webhook.delete(db).await?;
|
||||
|
||||
AuditLogEntryAction::WebhookDelete {
|
||||
webhook: webhook.id,
|
||||
name: webhook.name,
|
||||
channel: webhook.channel_id,
|
||||
}
|
||||
.insert(
|
||||
db,
|
||||
channel
|
||||
.server()
|
||||
.expect("Webhook created on non server channel")
|
||||
.to_string(),
|
||||
reason,
|
||||
user.id,
|
||||
Some(webhook.creator_id),
|
||||
)
|
||||
.await;
|
||||
|
||||
Ok(EmptyResponse)
|
||||
webhook.delete(db).await.map(|_| EmptyResponse)
|
||||
}
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
use revolt_result::{create_error, Error};
|
||||
use revolt_rocket_okapi::{OpenApiError, gen::OpenApiGenerator, request::{OpenApiFromRequest, RequestHeaderInput}, revolt_okapi::openapi3::{Parameter, ParameterValue}};
|
||||
use rocket::{
|
||||
http::Status,
|
||||
request::{FromRequest, Outcome, Request},
|
||||
};
|
||||
use schemars::schema::{InstanceType, SchemaObject, SingleOrVec};
|
||||
|
||||
/// Newtype for an audit log reason.
|
||||
///
|
||||
/// Extracts the reason from the `X-Audit-Log-Reason` header if provided.
|
||||
pub struct AuditLogReason(pub Option<String>);
|
||||
|
||||
#[async_trait]
|
||||
impl<'r> FromRequest<'r> for AuditLogReason {
|
||||
type Error = Error;
|
||||
|
||||
async fn from_request(req: &'r Request<'_>) -> Outcome<Self, Self::Error> {
|
||||
let reason = req.headers().get_one("x-audit-log-reason");
|
||||
|
||||
if reason.is_some_and(|str| str.len() > 512) {
|
||||
return Outcome::Error((Status::BadRequest, create_error!(HeaderTooLarge)));
|
||||
};
|
||||
|
||||
Outcome::Success(Self(reason.map(|str| str.to_string())))
|
||||
}
|
||||
}
|
||||
|
||||
impl OpenApiFromRequest<'_> for AuditLogReason {
|
||||
fn from_request_input(
|
||||
_gen: &mut OpenApiGenerator,
|
||||
_name: String,
|
||||
_required: bool,
|
||||
) -> Result<RequestHeaderInput, OpenApiError> {
|
||||
Ok(RequestHeaderInput::Parameter(Parameter {
|
||||
name: "X-Audit-Log-Reason".to_string(),
|
||||
description: Some("Reason for action which is stored in the audit log.".to_string()),
|
||||
allow_empty_value: false,
|
||||
required: false,
|
||||
deprecated: false,
|
||||
extensions: schemars::Map::new(),
|
||||
location: "header".to_string(),
|
||||
value: ParameterValue::Schema {
|
||||
allow_reserved: false,
|
||||
example: None,
|
||||
examples: None,
|
||||
explode: None,
|
||||
style: None,
|
||||
schema: SchemaObject {
|
||||
instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::String))),
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AuditLogReason> for Option<String> {
|
||||
fn from(value: AuditLogReason) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
pub mod audit_log_reason;
|
||||
pub mod ratelimits;
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -10,7 +10,7 @@ use tokio::sync::RwLock;
|
||||
|
||||
pub mod types;
|
||||
|
||||
const TENOR_API_BASE_URL: &str = "https://compat.gifbox.me/v2";
|
||||
const TENOR_API_BASE_URL: &str = "https://tenor.googleapis.com/v2";
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum TenorError {
|
||||
@@ -59,11 +59,7 @@ impl Tenor {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn request<T: DeserializeOwned>(
|
||||
&self,
|
||||
path: &str,
|
||||
query: &[Option<(&str, &str)>],
|
||||
) -> Result<Arc<T>, TenorError> {
|
||||
pub async fn request<T: DeserializeOwned>(&self, path: &str, query: &[Option<(&str, &str)>]) -> Result<Arc<T>, TenorError> {
|
||||
let response = self
|
||||
.client
|
||||
.get(format!("{TENOR_API_BASE_URL}{path}"))
|
||||
@@ -99,27 +95,24 @@ impl Tenor {
|
||||
}
|
||||
}
|
||||
|
||||
let res = self
|
||||
.coalescion
|
||||
.execute(unique_key.clone(), || async move {
|
||||
self.request::<types::PaginatedMediaResponse>(
|
||||
"/search",
|
||||
&[
|
||||
Some(("key", &self.key)),
|
||||
Some(("q", query)),
|
||||
Some(("client_key", "Gifbox")),
|
||||
Some(("media_filter", "webm,tinywebm")),
|
||||
Some(("locale", locale)),
|
||||
Some(("contentfilter", "high")),
|
||||
Some(("limit", &limit.to_string())),
|
||||
position.is_empty().then_some(("pos", position)),
|
||||
is_category.then_some(("component", "categories")),
|
||||
],
|
||||
)
|
||||
.await
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
let res = self.coalescion.execute(unique_key.clone(), || async move {
|
||||
self.request::<types::PaginatedMediaResponse>(
|
||||
"/search",
|
||||
&[
|
||||
Some(("key", &self.key)),
|
||||
Some(("q", query)),
|
||||
Some(("client_key", "Gifbox")),
|
||||
Some(("media_filter", "webm,tinywebm")),
|
||||
Some(("locale", locale)),
|
||||
Some(("contentfilter", "high")),
|
||||
Some(("limit", &limit.to_string())),
|
||||
position.is_empty().then_some(("pos", position)),
|
||||
is_category.then_some(("component", "categories"))
|
||||
]
|
||||
).await
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
if let Ok(resp) = &*res {
|
||||
self.cache.write().await.insert(unique_key, resp.clone());
|
||||
@@ -150,9 +143,8 @@ impl Tenor {
|
||||
Some(("client_key", "Gifbox")),
|
||||
Some(("locale", locale)),
|
||||
Some(("contentfilter", "high")),
|
||||
],
|
||||
)
|
||||
.await
|
||||
]
|
||||
).await
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -181,25 +173,22 @@ impl Tenor {
|
||||
}
|
||||
}
|
||||
|
||||
let res = self
|
||||
.coalescion
|
||||
.execute(unique_key.clone(), || async move {
|
||||
self.request::<types::PaginatedMediaResponse>(
|
||||
"/featured",
|
||||
&[
|
||||
Some(("key", &self.key)),
|
||||
Some(("client_key", "Gifbox")),
|
||||
Some(("media_filter", "webm,tinywebm")),
|
||||
Some(("locale", locale)),
|
||||
Some(("contentfilter", "high")),
|
||||
Some(("limit", &limit.to_string())),
|
||||
position.is_empty().then_some(("pos", position)),
|
||||
],
|
||||
)
|
||||
.await
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
let res = self.coalescion.execute(unique_key.clone(), || async move {
|
||||
self.request::<types::PaginatedMediaResponse>(
|
||||
"/featured",
|
||||
&[
|
||||
Some(("key", &self.key)),
|
||||
Some(("client_key", "Gifbox")),
|
||||
Some(("media_filter", "webm,tinywebm")),
|
||||
Some(("locale", locale)),
|
||||
Some(("contentfilter", "high")),
|
||||
Some(("limit", &limit.to_string())),
|
||||
position.is_empty().then_some(("pos", position)),
|
||||
]
|
||||
).await
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
if let Ok(resp) = &*res {
|
||||
self.featured.write().await.insert(unique_key, resp.clone());
|
||||
|
||||
@@ -419,22 +419,21 @@ impl Request {
|
||||
}
|
||||
|
||||
pub async fn url_is_blacklisted(url: &Url) -> Result<IPRequest> {
|
||||
let mut resolved_address: Option<IpAddr> = None;
|
||||
let resolved_address: IpAddr;
|
||||
|
||||
if let Some(host) = url.host() {
|
||||
match host {
|
||||
Host::Ipv4(ipv4) => {
|
||||
resolved_address = ipv4.into();
|
||||
if !IP_BLOCKLIST.is_allowed(&ipv4.to_string()) {
|
||||
return Err(create_error!(InvalidOperation));
|
||||
}
|
||||
resolved_address = Some(ipv4.into());
|
||||
}
|
||||
Host::Ipv6(ipv6) => {
|
||||
let string = ipv6.to_string();
|
||||
if string.contains("::ffff:") || !IP_BLOCKLIST.is_allowed(&string) {
|
||||
resolved_address = ipv6.into();
|
||||
if !IP_BLOCKLIST.is_allowed(&ipv6.to_string()) {
|
||||
return Err(create_error!(InvalidOperation));
|
||||
}
|
||||
resolved_address = Some(ipv6.into());
|
||||
}
|
||||
Host::Domain(domain) => {
|
||||
let domain = domain.to_string();
|
||||
@@ -450,7 +449,7 @@ impl Request {
|
||||
|
||||
// Second step: resolve the IP and check the blocklist
|
||||
let resolver = CachedDnsResolver {};
|
||||
if let Ok(resolved_ips) = resolver
|
||||
if let Ok(mut resolved_ip) = resolver
|
||||
.resolve(
|
||||
Name::from_str(&domain)
|
||||
.map_err(|_| create_error!(ProxyError))
|
||||
@@ -458,14 +457,16 @@ impl Request {
|
||||
)
|
||||
.await
|
||||
{
|
||||
for resolved in resolved_ips {
|
||||
resolved_address = Some(resolved.ip()); // last resolved ip will be the one we hit as a consequence of this for loop.
|
||||
let resolved_string = resolved_address.unwrap().to_string();
|
||||
if let Some(resolved_ip) = resolved_ip.next() {
|
||||
resolved_address = resolved_ip.ip();
|
||||
let resolved_string = resolved_address.to_string();
|
||||
if !IP_BLOCKLIST.is_allowed(&resolved_string)
|
||||
|| resolved_string.contains("::ffff:")
|
||||
{
|
||||
return Err(create_error!(InvalidOperation));
|
||||
}
|
||||
} else {
|
||||
return Err(create_error!(InvalidOperation));
|
||||
}
|
||||
} else {
|
||||
return Err(create_error!(ProxyError));
|
||||
@@ -476,13 +477,9 @@ impl Request {
|
||||
return Err(create_error!(ProxyError));
|
||||
};
|
||||
|
||||
if resolved_address.is_none() {
|
||||
return Err(create_error!(InvalidOperation));
|
||||
}
|
||||
|
||||
Ok(IPRequest {
|
||||
url: url.clone(),
|
||||
ip: resolved_address.unwrap(),
|
||||
ip: resolved_address,
|
||||
blocked: false,
|
||||
})
|
||||
}
|
||||
|
||||
+2
-2
@@ -22,8 +22,8 @@
|
||||
"@scalar/docusaurus": "^0.7.21",
|
||||
"clsx": "^2.0.0",
|
||||
"prism-react-renderer": "^2.3.0",
|
||||
"react": "^19.2.7",
|
||||
"react-dom": "^19.2.7"
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/module-type-aliases": "3.9.2",
|
||||
|
||||
Reference in New Issue
Block a user