Merge branch 'main' into feat/admin-api
# Conflicts: # crates/core/database/src/util/mod.rs # crates/core/result/src/axum.rs
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "revolt-delta"
|
||||
version = "0.12.1"
|
||||
version = "0.13.7"
|
||||
license = "AGPL-3.0-or-later"
|
||||
authors = ["Paul Makles <paulmakles@gmail.com>"]
|
||||
edition = "2018"
|
||||
@@ -64,7 +64,7 @@ schemars = { workspace = true }
|
||||
revolt_rocket_okapi = { workspace = true, features = ["swagger"] }
|
||||
|
||||
# rabbit
|
||||
amqprs = { workspace = true }
|
||||
lapin = { workspace = true, features = ["tokio"] }
|
||||
|
||||
# core
|
||||
authifier = { workspace = true }
|
||||
|
||||
@@ -9,8 +9,7 @@ pub mod routes;
|
||||
pub mod util;
|
||||
|
||||
use revolt_config::config;
|
||||
use revolt_database::events::client::EventV1;
|
||||
use revolt_database::AMQP;
|
||||
use revolt_database::{AMQP, events::client::EventV1};
|
||||
use revolt_ratelimits::rocket as ratelimiter;
|
||||
use rocket::{Build, Rocket};
|
||||
use rocket_cors::{AllowedOrigins, CorsOptions};
|
||||
@@ -18,14 +17,10 @@ use rocket_prometheus::PrometheusMetrics;
|
||||
use std::net::Ipv4Addr;
|
||||
use std::str::FromStr;
|
||||
|
||||
use amqprs::{
|
||||
channel::ExchangeDeclareArguments,
|
||||
connection::{Connection, OpenConnectionArguments},
|
||||
};
|
||||
use async_std::channel::unbounded;
|
||||
use authifier::AuthifierEvent;
|
||||
use rocket::data::ToByteUnit;
|
||||
use revolt_database::voice::VoiceClient;
|
||||
use rocket::data::ToByteUnit;
|
||||
|
||||
pub async fn web() -> Rocket<Build> {
|
||||
// Get settings
|
||||
@@ -36,7 +31,6 @@ pub async fn web() -> Rocket<Build> {
|
||||
|
||||
// Setup database
|
||||
let db = revolt_database::DatabaseInfo::Auto.connect().await.unwrap();
|
||||
log::info!("database_here {db:?}");
|
||||
db.migrate_database().await.unwrap();
|
||||
|
||||
// Setup Authifier event channel
|
||||
@@ -93,49 +87,11 @@ pub async fn web() -> Rocket<Build> {
|
||||
)
|
||||
.into();
|
||||
|
||||
let swagger_0_8 = revolt_rocket_okapi::swagger_ui::make_swagger_ui(
|
||||
&revolt_rocket_okapi::swagger_ui::SwaggerUIConfig {
|
||||
url: "/0.8/openapi.json".to_owned(),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.into();
|
||||
|
||||
let swagger_0_8 = revolt_rocket_okapi::swagger_ui::make_swagger_ui(
|
||||
&revolt_rocket_okapi::swagger_ui::SwaggerUIConfig {
|
||||
url: "/0.8/openapi.json".to_owned(),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.into();
|
||||
|
||||
// Voice handler
|
||||
let voice_client = VoiceClient::new(config.api.livekit.nodes.clone());
|
||||
// Configure Rabbit
|
||||
let connection = Connection::open(&OpenConnectionArguments::new(
|
||||
&config.rabbit.host,
|
||||
config.rabbit.port,
|
||||
&config.rabbit.username,
|
||||
&config.rabbit.password,
|
||||
))
|
||||
.await
|
||||
.expect("Failed to connect to RabbitMQ");
|
||||
|
||||
let channel = connection
|
||||
.open_channel(None)
|
||||
.await
|
||||
.expect("Failed to open RabbitMQ channel");
|
||||
|
||||
channel
|
||||
.exchange_declare(
|
||||
ExchangeDeclareArguments::new(&config.pushd.exchange, "direct")
|
||||
.durable(true)
|
||||
.finish(),
|
||||
)
|
||||
.await
|
||||
.expect("Failed to declare exchange");
|
||||
|
||||
let amqp = AMQP::new(connection, channel);
|
||||
let amqp = AMQP::new_auto().await;
|
||||
|
||||
// Launch background task workers
|
||||
revolt_database::tasks::start_workers(db.clone(), amqp.clone());
|
||||
@@ -153,7 +109,6 @@ pub async fn web() -> Rocket<Build> {
|
||||
.mount("/", rocket_cors::catch_all_options_routes())
|
||||
.mount("/", ratelimiter::routes())
|
||||
.mount("/swagger/", swagger)
|
||||
.mount("/0.8/swagger/", swagger_0_8)
|
||||
.manage(authifier)
|
||||
.manage(db)
|
||||
.manage(amqp)
|
||||
@@ -166,6 +121,7 @@ pub async fn web() -> Rocket<Build> {
|
||||
limits: rocket::data::Limits::default().limit("string", 5.megabytes()),
|
||||
address: Ipv4Addr::new(0, 0, 0, 0).into(),
|
||||
port: 14702,
|
||||
ip_header: Some("X-Forwarded-For".into()),
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use revolt_database::{
|
||||
util::{permissions::DatabasePermissionQuery, reference::Reference},
|
||||
Database, User,
|
||||
Database, User, AMQP,
|
||||
};
|
||||
use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
|
||||
use revolt_result::{create_error, Result};
|
||||
@@ -14,6 +14,7 @@ use rocket_empty::EmptyResponse;
|
||||
#[put("/<target>/ack/<message>")]
|
||||
pub async fn ack(
|
||||
db: &State<Database>,
|
||||
amqp: &State<AMQP>,
|
||||
user: User,
|
||||
target: Reference<'_>,
|
||||
message: Reference<'_>,
|
||||
@@ -29,7 +30,7 @@ pub async fn ack(
|
||||
.throw_if_lacking_channel_permission(ChannelPermission::ViewChannel)?;
|
||||
|
||||
channel
|
||||
.ack(&user.id, message.id)
|
||||
.ack(&user.id, message.id, amqp)
|
||||
.await
|
||||
.map(|_| EmptyResponse)
|
||||
}
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use redis_kiss::{get_connection, redis, AsyncCommands};
|
||||
use revolt_database::events::client::EventV1;
|
||||
use revolt_database::util::permissions::DatabasePermissionQuery;
|
||||
use revolt_database::{
|
||||
util::idempotency::IdempotencyKey, util::reference::Reference, Database, User,
|
||||
};
|
||||
use revolt_database::{Channel, Interactions, Message, AMQP};
|
||||
use revolt_models::v0;
|
||||
use revolt_models::v0::ChannelSlowmode;
|
||||
use revolt_permissions::PermissionQuery;
|
||||
use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
|
||||
use revolt_result::{create_error, Result};
|
||||
@@ -84,6 +86,16 @@ pub async fn message_send(
|
||||
.await
|
||||
.unwrap_or(None);
|
||||
|
||||
if set_result.is_some() {
|
||||
let idx_key = format!("slowmode_idx:{}", user.id);
|
||||
conn.sadd::<_, _, ()>(&idx_key, channel_id.as_str())
|
||||
.await
|
||||
.ok();
|
||||
conn.expire::<_, ()>(&idx_key, *channel_slowmode as usize)
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
|
||||
// If `set_result` is None, the `NX` condition failed because the key already exists.
|
||||
// This means the user is currently in slowmode.
|
||||
if set_result.is_none() {
|
||||
@@ -92,10 +104,29 @@ pub async fn message_send(
|
||||
|
||||
// Redis returns positive integers for valid TTLs
|
||||
if ttl > 0 {
|
||||
EventV1::UserSlowmodes {
|
||||
slowmodes: vec![ChannelSlowmode {
|
||||
channel_id: channel_id.to_string(),
|
||||
duration: *channel_slowmode,
|
||||
retry_after: ttl as u64,
|
||||
}],
|
||||
}
|
||||
.private(user.id.clone())
|
||||
.await;
|
||||
return Err(create_error!(InSlowmode {
|
||||
retry_after: ttl as u64
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
EventV1::UserSlowmodes {
|
||||
slowmodes: vec![ChannelSlowmode {
|
||||
channel_id: channel_id.to_string(),
|
||||
duration: *channel_slowmode,
|
||||
retry_after: *channel_slowmode,
|
||||
}],
|
||||
}
|
||||
.private(user.id.clone())
|
||||
.await;
|
||||
}
|
||||
}
|
||||
// If Redis connection fails, just skip the slowmode check
|
||||
|
||||
212
crates/delta/src/routes/customisation/emoji_edit.rs
Normal file
212
crates/delta/src/routes/customisation/emoji_edit.rs
Normal file
@@ -0,0 +1,212 @@
|
||||
use revolt_database::{
|
||||
util::{permissions::DatabasePermissionQuery, reference::Reference},
|
||||
Database, EmojiParent, PartialEmoji, 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;
|
||||
|
||||
/// # Edit Emoji
|
||||
///
|
||||
/// Edit an emoji by its id.
|
||||
#[openapi(tag = "Emojis")]
|
||||
#[patch("/emoji/<emoji_id>", data = "<data>")]
|
||||
pub async fn edit_emoji(
|
||||
db: &State<Database>,
|
||||
user: User,
|
||||
emoji_id: Reference<'_>,
|
||||
data: Json<v0::DataEditEmoji>,
|
||||
) -> Result<Json<v0::Emoji>> {
|
||||
let data = data.into_inner();
|
||||
data.validate().map_err(|error| {
|
||||
create_error!(FailedValidation {
|
||||
error: error.to_string()
|
||||
})
|
||||
})?;
|
||||
|
||||
let mut emoji = emoji_id.as_emoji(db).await?;
|
||||
|
||||
match &emoji.parent {
|
||||
EmojiParent::Server { id } => {
|
||||
let server = db.fetch_server(id.as_str()).await?;
|
||||
|
||||
let mut query = DatabasePermissionQuery::new(db, &user).server(&server);
|
||||
calculate_server_permissions(&mut query)
|
||||
.await
|
||||
.throw_if_lacking_channel_permission(ChannelPermission::ManageCustomisation)?;
|
||||
}
|
||||
EmojiParent::Detached => return Err(create_error!(NotAuthenticated)),
|
||||
}
|
||||
|
||||
if data.name.is_none() {
|
||||
return Ok(Json(emoji.into()));
|
||||
}
|
||||
|
||||
let partial = PartialEmoji { name: data.name };
|
||||
emoji.update(db, partial).await?;
|
||||
|
||||
Ok(Json(emoji.into()))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::util::test::TestHarness;
|
||||
use revolt_database::{Emoji, EmojiParent, Member};
|
||||
use revolt_models::v0;
|
||||
use rocket::http::{ContentType, Header, Status};
|
||||
use ulid::Ulid;
|
||||
|
||||
#[rocket::async_test]
|
||||
async fn edit_emoji_name_as_creator() {
|
||||
let harness = TestHarness::new().await;
|
||||
let (_, session, user) = harness.new_user().await;
|
||||
let (server, _) = harness.new_server(&user).await;
|
||||
|
||||
let emoji_id = Ulid::new().to_string();
|
||||
let emoji = Emoji {
|
||||
id: emoji_id.clone(),
|
||||
parent: EmojiParent::Server {
|
||||
id: server.id.clone(),
|
||||
},
|
||||
creator_id: user.id.clone(),
|
||||
name: "initial_name".to_string(),
|
||||
animated: false,
|
||||
nsfw: false,
|
||||
};
|
||||
emoji.create(&harness.db).await.expect("`Emoji` created");
|
||||
|
||||
let response = harness
|
||||
.client
|
||||
.patch(format!("/custom/emoji/{emoji_id}"))
|
||||
.header(Header::new("x-session-token", session.token.to_string()))
|
||||
.header(ContentType::JSON)
|
||||
.body(
|
||||
json!(v0::DataEditEmoji {
|
||||
name: Some("renamed_emoji".to_string()),
|
||||
})
|
||||
.to_string(),
|
||||
)
|
||||
.dispatch()
|
||||
.await;
|
||||
|
||||
assert_eq!(response.status(), Status::Ok);
|
||||
|
||||
let edited: v0::Emoji = response.into_json().await.expect("`Emoji`");
|
||||
assert_eq!(edited.name, "renamed_emoji");
|
||||
}
|
||||
|
||||
#[rocket::async_test]
|
||||
async fn reject_invalid_emoji_name() {
|
||||
let harness = TestHarness::new().await;
|
||||
let (_, session, user) = harness.new_user().await;
|
||||
let (server, _) = harness.new_server(&user).await;
|
||||
|
||||
let emoji_id = Ulid::new().to_string();
|
||||
let emoji = Emoji {
|
||||
id: emoji_id.clone(),
|
||||
parent: EmojiParent::Server {
|
||||
id: server.id.clone(),
|
||||
},
|
||||
creator_id: user.id.clone(),
|
||||
name: "valid_name".to_string(),
|
||||
animated: false,
|
||||
nsfw: false,
|
||||
};
|
||||
emoji.create(&harness.db).await.expect("`Emoji` created");
|
||||
|
||||
let response = harness
|
||||
.client
|
||||
.patch(format!("/custom/emoji/{emoji_id}"))
|
||||
.header(Header::new("x-session-token", session.token.to_string()))
|
||||
.header(ContentType::JSON)
|
||||
.body(
|
||||
json!(v0::DataEditEmoji {
|
||||
name: Some("Invalid Name".to_string()),
|
||||
})
|
||||
.to_string(),
|
||||
)
|
||||
.dispatch()
|
||||
.await;
|
||||
|
||||
assert_eq!(response.status(), Status::BadRequest);
|
||||
}
|
||||
|
||||
#[rocket::async_test]
|
||||
async fn reject_edit_for_detached_emoji() {
|
||||
let harness = TestHarness::new().await;
|
||||
let (_, session, user) = harness.new_user().await;
|
||||
|
||||
let emoji_id = Ulid::new().to_string();
|
||||
let emoji = Emoji {
|
||||
id: emoji_id.clone(),
|
||||
parent: EmojiParent::Detached,
|
||||
creator_id: user.id.clone(),
|
||||
name: "detached_name".to_string(),
|
||||
animated: false,
|
||||
nsfw: false,
|
||||
};
|
||||
emoji.create(&harness.db).await.expect("`Emoji` created");
|
||||
|
||||
let response = harness
|
||||
.client
|
||||
.patch(format!("/custom/emoji/{emoji_id}"))
|
||||
.header(Header::new("x-session-token", session.token.to_string()))
|
||||
.header(ContentType::JSON)
|
||||
.body(
|
||||
json!(v0::DataEditEmoji {
|
||||
name: Some("should_not_apply".to_string()),
|
||||
})
|
||||
.to_string(),
|
||||
)
|
||||
.dispatch()
|
||||
.await;
|
||||
|
||||
assert_eq!(response.status(), Status::Unauthorized);
|
||||
}
|
||||
|
||||
#[rocket::async_test]
|
||||
async fn reject_edit_for_creator_without_manage_customisation() {
|
||||
let harness = TestHarness::new().await;
|
||||
let (_, _, owner) = harness.new_user().await;
|
||||
let (_, creator_session, creator) = harness.new_user().await;
|
||||
let (server, _) = harness.new_server(&owner).await;
|
||||
|
||||
Member::create(&harness.db, &server, &creator, None)
|
||||
.await
|
||||
.expect("`Member` created");
|
||||
|
||||
let emoji_id = Ulid::new().to_string();
|
||||
let emoji = Emoji {
|
||||
id: emoji_id.clone(),
|
||||
parent: EmojiParent::Server {
|
||||
id: server.id.clone(),
|
||||
},
|
||||
creator_id: creator.id.clone(),
|
||||
name: "member_uploaded_name".to_string(),
|
||||
animated: false,
|
||||
nsfw: false,
|
||||
};
|
||||
emoji.create(&harness.db).await.expect("`Emoji` created");
|
||||
|
||||
let response = harness
|
||||
.client
|
||||
.patch(format!("/custom/emoji/{emoji_id}"))
|
||||
.header(Header::new(
|
||||
"x-session-token",
|
||||
creator_session.token.to_string(),
|
||||
))
|
||||
.header(ContentType::JSON)
|
||||
.body(
|
||||
json!(v0::DataEditEmoji {
|
||||
name: Some("renamed_without_permission".to_string()),
|
||||
})
|
||||
.to_string(),
|
||||
)
|
||||
.dispatch()
|
||||
.await;
|
||||
|
||||
assert_eq!(response.status(), Status::Forbidden);
|
||||
}
|
||||
}
|
||||
@@ -3,12 +3,14 @@ use rocket::Route;
|
||||
|
||||
mod emoji_create;
|
||||
mod emoji_delete;
|
||||
mod emoji_edit;
|
||||
mod emoji_fetch;
|
||||
|
||||
pub fn routes() -> (Vec<Route>, OpenApi) {
|
||||
openapi_get_routes_spec![
|
||||
emoji_create::create_emoji,
|
||||
emoji_delete::delete_emoji,
|
||||
emoji_edit::edit_emoji,
|
||||
emoji_fetch::fetch_emoji
|
||||
]
|
||||
}
|
||||
|
||||
@@ -57,6 +57,8 @@ pub struct RevoltFeatures {
|
||||
pub livekit: VoiceFeature,
|
||||
/// Limits
|
||||
pub limits: LimitsConfig,
|
||||
/// Legal links
|
||||
pub legal_links: LegalLinks,
|
||||
}
|
||||
|
||||
/// # Limits For Users
|
||||
@@ -70,6 +72,17 @@ pub struct LimitsConfig {
|
||||
pub default: UserLimits,
|
||||
}
|
||||
|
||||
/// # Legal links
|
||||
#[derive(Serialize, JsonSchema, Debug)]
|
||||
pub struct LegalLinks {
|
||||
/// Terms of Service URL
|
||||
pub terms_of_service: String,
|
||||
/// Privacy Policy URL
|
||||
pub privacy_policy: String,
|
||||
/// Guidelines URL
|
||||
pub guidelines: String,
|
||||
}
|
||||
|
||||
/// # Global limits
|
||||
#[derive(Serialize, JsonSchema, Debug)]
|
||||
pub struct GlobalLimits {
|
||||
@@ -238,6 +251,11 @@ pub async fn root() -> Result<Json<RevoltConfig>> {
|
||||
new_user: UserLimits::from_feature_limits(config.features.limits.new_user),
|
||||
default: UserLimits::from_feature_limits(config.features.limits.default),
|
||||
},
|
||||
legal_links: LegalLinks {
|
||||
terms_of_service: config.features.legal_links.terms_of_service,
|
||||
privacy_policy: config.features.legal_links.privacy_policy,
|
||||
guidelines: config.features.legal_links.guidelines,
|
||||
},
|
||||
},
|
||||
ws: config.hosts.events,
|
||||
app: config.hosts.app,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use revolt_database::{
|
||||
util::{permissions::DatabasePermissionQuery, reference::Reference},
|
||||
voice::{sync_voice_permissions, VoiceClient},
|
||||
Database, PartialRole, User
|
||||
Database, File, PartialRole, User,
|
||||
};
|
||||
use revolt_models::v0;
|
||||
use revolt_permissions::{calculate_server_permissions, ChannelPermission};
|
||||
@@ -47,14 +47,27 @@ pub async fn edit(
|
||||
name,
|
||||
colour,
|
||||
hoist,
|
||||
icon,
|
||||
remove,
|
||||
..
|
||||
} = data;
|
||||
|
||||
if remove.contains(&v0::FieldsRole::Icon) {
|
||||
if let Some(existing_icon) = &role.icon {
|
||||
db.mark_attachment_as_deleted(&existing_icon.id).await?;
|
||||
}
|
||||
}
|
||||
|
||||
let mut final_icon = None;
|
||||
if let Some(icon_id) = icon {
|
||||
final_icon = Some(File::use_role_icon(db, &icon_id, &role_id, &user.id).await?);
|
||||
}
|
||||
|
||||
let partial = PartialRole {
|
||||
name,
|
||||
colour,
|
||||
hoist,
|
||||
icon: final_icon,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
@@ -69,8 +82,9 @@ pub async fn edit(
|
||||
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?;
|
||||
};
|
||||
sync_voice_permissions(db, voice_client, &channel, Some(&server), Some(&role_id))
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(Json(role.into()))
|
||||
} else {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use revolt_database::{
|
||||
util::{permissions::DatabasePermissionQuery, reference::Reference},
|
||||
Database, User,
|
||||
util::{acker, permissions::DatabasePermissionQuery, reference::Reference},
|
||||
Database, User, AMQP,
|
||||
};
|
||||
use revolt_permissions::PermissionQuery;
|
||||
use revolt_result::{create_error, Result};
|
||||
@@ -12,7 +12,12 @@ use rocket_empty::EmptyResponse;
|
||||
/// Mark all channels in a server as read.
|
||||
#[openapi(tag = "Server Information")]
|
||||
#[put("/<target>/ack")]
|
||||
pub async fn ack(db: &State<Database>, user: User, target: Reference<'_>) -> Result<EmptyResponse> {
|
||||
pub async fn ack(
|
||||
db: &State<Database>,
|
||||
amqp: &State<AMQP>,
|
||||
user: User,
|
||||
target: Reference<'_>,
|
||||
) -> Result<EmptyResponse> {
|
||||
if user.bot.is_some() {
|
||||
return Err(create_error!(IsBot));
|
||||
}
|
||||
@@ -23,7 +28,6 @@ pub async fn ack(db: &State<Database>, user: User, target: Reference<'_>) -> Res
|
||||
return Err(create_error!(NotFound));
|
||||
}
|
||||
|
||||
db.acknowledge_channels(&user.id, &server.channels)
|
||||
.await
|
||||
.map(|_| EmptyResponse)
|
||||
acker::ack_server(&user, &server, db, amqp).await?;
|
||||
Ok(EmptyResponse)
|
||||
}
|
||||
|
||||
@@ -1,19 +1,23 @@
|
||||
use rocket::Route;
|
||||
use revolt_rocket_okapi::revolt_okapi::openapi3::OpenApi;
|
||||
use rocket::Route;
|
||||
|
||||
mod webhook_delete;
|
||||
mod webhook_delete_message;
|
||||
mod webhook_delete_token;
|
||||
mod webhook_edit;
|
||||
mod webhook_edit_message;
|
||||
mod webhook_edit_token;
|
||||
mod webhook_execute;
|
||||
mod webhook_fetch_token;
|
||||
mod webhook_fetch;
|
||||
mod webhook_execute_github;
|
||||
mod webhook_fetch;
|
||||
mod webhook_fetch_token;
|
||||
|
||||
pub fn routes() -> (Vec<Route>, OpenApi) {
|
||||
openapi_get_routes_spec![
|
||||
webhook_delete_message::webhook_delete_message,
|
||||
webhook_delete_token::webhook_delete_token,
|
||||
webhook_delete::webhook_delete,
|
||||
webhook_edit_message::webhook_edit_message,
|
||||
webhook_edit_token::webhook_edit_token,
|
||||
webhook_edit::webhook_edit,
|
||||
webhook_execute_github::webhook_execute_github,
|
||||
|
||||
27
crates/delta/src/routes/webhooks/webhook_delete_message.rs
Normal file
27
crates/delta/src/routes/webhooks/webhook_delete_message.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
use revolt_database::{util::reference::Reference, Database};
|
||||
use revolt_result::{create_error, Result};
|
||||
use rocket::State;
|
||||
use rocket_empty::EmptyResponse;
|
||||
|
||||
/// # Deletes a webhook message
|
||||
///
|
||||
/// Deletes a message sent by a webhook
|
||||
#[openapi(tag = "Webhooks")]
|
||||
#[delete("/<webhook_id>/<token>/<message_id>")]
|
||||
pub async fn webhook_delete_message(
|
||||
db: &State<Database>,
|
||||
webhook_id: Reference<'_>,
|
||||
token: String,
|
||||
message_id: Reference<'_>,
|
||||
) -> Result<EmptyResponse> {
|
||||
let webhook = webhook_id.as_webhook(db).await?;
|
||||
webhook.assert_token(&token)?;
|
||||
|
||||
let message = message_id.as_message(db).await?;
|
||||
|
||||
if message.author != webhook.id {
|
||||
return Err(create_error!(CannotDeleteMessage));
|
||||
}
|
||||
|
||||
message.delete(db).await.map(|_| EmptyResponse)
|
||||
}
|
||||
84
crates/delta/src/routes/webhooks/webhook_edit_message.rs
Normal file
84
crates/delta/src/routes/webhooks/webhook_edit_message.rs
Normal file
@@ -0,0 +1,84 @@
|
||||
use iso8601_timestamp::Timestamp;
|
||||
use revolt_config::config;
|
||||
use revolt_database::{
|
||||
tasks::process_embeds::queue, util::reference::Reference, Database, Message, PartialMessage,
|
||||
};
|
||||
use revolt_models::v0::{self, DataEditMessage, Embed};
|
||||
use revolt_models::validator::Validate;
|
||||
use revolt_result::{create_error, Result};
|
||||
use rocket::{serde::json::Json, State};
|
||||
|
||||
/// # Edits a webhook message
|
||||
///
|
||||
/// Edits a message sent by a webhook
|
||||
#[openapi(tag = "Webhooks")]
|
||||
#[patch("/<webhook_id>/<token>/<message_id>", data = "<data>")]
|
||||
pub async fn webhook_edit_message(
|
||||
db: &State<Database>,
|
||||
webhook_id: Reference<'_>,
|
||||
token: String,
|
||||
message_id: Reference<'_>,
|
||||
data: Json<DataEditMessage>,
|
||||
) -> Result<Json<v0::Message>> {
|
||||
let edit = data.into_inner();
|
||||
edit.validate().map_err(|error| {
|
||||
create_error!(FailedValidation {
|
||||
error: error.to_string()
|
||||
})
|
||||
})?;
|
||||
|
||||
Message::validate_sum(
|
||||
&edit.content,
|
||||
edit.embeds.as_deref().unwrap_or_default(),
|
||||
config().await.features.limits.default.message_length,
|
||||
)?;
|
||||
|
||||
let webhook = webhook_id.as_webhook(db).await?;
|
||||
webhook.assert_token(&token)?;
|
||||
|
||||
let mut message = message_id.as_message(db).await?;
|
||||
if message.author != webhook.id {
|
||||
return Err(create_error!(CannotEditMessage));
|
||||
}
|
||||
|
||||
message.edited = Some(Timestamp::now_utc());
|
||||
let mut partial = PartialMessage {
|
||||
edited: message.edited,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// 1. Handle content update
|
||||
if let Some(content) = &edit.content {
|
||||
partial.content = Some(content.clone());
|
||||
}
|
||||
|
||||
// 2. Clear any auto generated embeds
|
||||
let mut new_embeds = vec![];
|
||||
if let Some(embeds) = &message.embeds {
|
||||
for embed in embeds {
|
||||
if let Embed::Text(embed) = embed {
|
||||
new_embeds.push(Embed::Text(embed.clone()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Replace if we are given new embeds
|
||||
if let Some(embeds) = edit.embeds {
|
||||
new_embeds.clear();
|
||||
|
||||
for embed in embeds {
|
||||
new_embeds.push(message.create_embed(db, embed).await?);
|
||||
}
|
||||
}
|
||||
|
||||
partial.embeds = Some(new_embeds);
|
||||
|
||||
message.update(db, partial, vec![]).await?;
|
||||
|
||||
// Queue up a task for processing embeds
|
||||
if let Some(content) = edit.content {
|
||||
queue(message.channel.to_string(), message.id.to_string(), content).await;
|
||||
}
|
||||
|
||||
Ok(Json(message.into_model(None, None)))
|
||||
}
|
||||
@@ -4,7 +4,7 @@ use authifier::{
|
||||
};
|
||||
use futures::StreamExt;
|
||||
use rand::Rng;
|
||||
use redis_kiss::redis::aio::PubSub;
|
||||
use redis_kiss::{redis::aio::PubSub};
|
||||
use revolt_database::{
|
||||
events::client::EventV1, Channel, Database, Member, Message, PartialRole, Server, User, AMQP,
|
||||
};
|
||||
@@ -25,8 +25,6 @@ pub struct TestHarness {
|
||||
|
||||
impl TestHarness {
|
||||
pub async fn new() -> TestHarness {
|
||||
let config = revolt_config::config().await;
|
||||
|
||||
let client = Client::tracked(crate::web().await)
|
||||
.await
|
||||
.expect("valid rocket instance");
|
||||
@@ -49,19 +47,7 @@ impl TestHarness {
|
||||
.expect("`Authifier`")
|
||||
.clone();
|
||||
|
||||
let connection = amqprs::connection::Connection::open(
|
||||
&amqprs::connection::OpenConnectionArguments::new(
|
||||
&config.rabbit.host,
|
||||
config.rabbit.port,
|
||||
&config.rabbit.username,
|
||||
&config.rabbit.password,
|
||||
),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let channel = connection.open_channel(None).await.unwrap();
|
||||
|
||||
let amqp = AMQP::new(connection, channel);
|
||||
let amqp = AMQP::new_auto().await;
|
||||
|
||||
TestHarness {
|
||||
client,
|
||||
|
||||
Reference in New Issue
Block a user