refactor(delta): port routes to core webhook models

This commit is contained in:
Paul Makles
2023-06-03 13:01:43 +01:00
parent f9f5a30e2c
commit 23188032ca
14 changed files with 647 additions and 471 deletions

60
Cargo.lock generated
View File

@@ -2867,7 +2867,10 @@ dependencies = [
"mongodb", "mongodb",
"nanoid", "nanoid",
"once_cell", "once_cell",
"redis-kiss",
"revolt-models",
"revolt-permissions", "revolt-permissions",
"revolt-presence",
"revolt-result", "revolt-result",
"revolt_optional_struct", "revolt_optional_struct",
"rocket", "rocket",
@@ -2902,6 +2905,7 @@ dependencies = [
"revolt-database", "revolt-database",
"revolt-models", "revolt-models",
"revolt-quark", "revolt-quark",
"revolt-result",
"revolt_rocket_okapi", "revolt_rocket_okapi",
"rocket", "rocket",
"rocket_authifier", "rocket_authifier",
@@ -2911,7 +2915,7 @@ dependencies = [
"serde_json", "serde_json",
"ulid 0.4.1", "ulid 0.4.1",
"url", "url",
"validator 0.14.0", "validator 0.16.0",
"vergen", "vergen",
] ]
@@ -2919,10 +2923,10 @@ dependencies = [
name = "revolt-models" name = "revolt-models"
version = "0.0.2" version = "0.0.2"
dependencies = [ dependencies = [
"revolt-database", "revolt_optional_struct",
"revolt-presence",
"schemars", "schemars",
"serde", "serde",
"validator 0.16.0",
] ]
[[package]] [[package]]
@@ -2981,6 +2985,7 @@ dependencies = [
"redis-kiss", "redis-kiss",
"regex", "regex",
"reqwest", "reqwest",
"revolt-models",
"revolt-presence", "revolt-presence",
"revolt-result", "revolt-result",
"revolt_okapi", "revolt_okapi",
@@ -2995,7 +3000,7 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"ulid 0.5.0", "ulid 0.5.0",
"validator 0.14.0", "validator 0.16.0",
"web-push", "web-push",
] ]
@@ -3003,8 +3008,12 @@ dependencies = [
name = "revolt-result" name = "revolt-result"
version = "0.0.2" version = "0.0.2"
dependencies = [ dependencies = [
"revolt_okapi",
"revolt_rocket_okapi",
"rocket",
"schemars", "schemars",
"serde", "serde",
"serde_json",
] ]
[[package]] [[package]]
@@ -4378,23 +4387,6 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "validator"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d0f08911ab0fee2c5009580f04615fa868898ee57de10692a45da0c3bcc3e5e"
dependencies = [
"idna",
"lazy_static",
"regex",
"serde",
"serde_derive",
"serde_json",
"url",
"validator_derive",
"validator_types",
]
[[package]] [[package]]
name = "validator" name = "validator"
version = "0.15.0" version = "0.15.0"
@@ -4411,10 +4403,26 @@ dependencies = [
] ]
[[package]] [[package]]
name = "validator_derive" name = "validator"
version = "0.14.0" version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d85135714dba11a1bd0b3eb1744169266f1a38977bf4e3ff5e2e1acb8c2b7eee" checksum = "32ad5bf234c7d3ad1042e5252b7eddb2c4669ee23f32c7dd0e9b7705f07ef591"
dependencies = [
"idna",
"lazy_static",
"regex",
"serde",
"serde_derive",
"serde_json",
"url",
"validator_derive",
]
[[package]]
name = "validator_derive"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc44ca3088bb3ba384d9aecf40c6a23a676ce23e09bdaca2073d99c207f864af"
dependencies = [ dependencies = [
"if_chain", "if_chain",
"lazy_static", "lazy_static",
@@ -4428,9 +4436,9 @@ dependencies = [
[[package]] [[package]]
name = "validator_types" name = "validator_types"
version = "0.14.0" version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ded9d97e1d42327632f5f3bae6403c04886e2de3036261ef42deebd931a6a291" checksum = "111abfe30072511849c5910134e8baf8dc05de4c0e5903d681cbd5c9c4d611e3"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"syn 1.0.107", "syn 1.0.107",

View File

@@ -31,7 +31,7 @@ nanoid = "0.4.0"
# serde # serde
serde_json = "1.0.57" serde_json = "1.0.57"
serde = { version = "1.0.115", features = ["derive"] } serde = { version = "1.0.115", features = ["derive"] }
validator = { version = "0.14", features = ["derive"] } validator = { version = "0.16", features = ["derive"] }
# async # async
futures = "0.3.8" futures = "0.3.8"
@@ -56,8 +56,9 @@ revolt_rocket_okapi = { version = "0.9.1", features = [ "swagger" ] }
revolt-quark = { path = "../quark" } revolt-quark = { path = "../quark" }
# core # core
revolt-database = { path = "../core/database", features = [ "rocket-impl" ] } revolt-database = { path = "../core/database", features = [ "rocket-impl", "redis-is-patched" ] }
revolt-models = { path = "../core/models", features = [ "schemas", "redis-is-patched" ] } revolt-models = { path = "../core/models", features = [ "schemas", "validator" ] }
revolt-result = { path = "../core/result", features = [ "rocket", "okapi" ] }
[build-dependencies] [build-dependencies]
vergen = "7.5.0" vergen = "7.5.0"

View File

@@ -23,11 +23,12 @@ pub async fn fetch_bot(
} }
Ok(Json(FetchBotResponse { Ok(Json(FetchBotResponse {
user: revolt_models::v0::User::from( user: db
db.fetch_user(&bot.id).await.map_err(Error::from_core)?, .fetch_user(&bot.id)
None, .await
) .map_err(Error::from_core)?
.await, .into(None)
.await,
bot: bot.into(), bot: bot.into(),
})) }))
} }

View File

@@ -21,5 +21,5 @@ pub async fn fetch_public_bot(
} }
let user = db.fetch_user(&bot.id).await.map_err(Error::from_core)?; let user = db.fetch_user(&bot.id).await.map_err(Error::from_core)?;
Ok(Json(PublicBot::from(bot, user))) Ok(Json(bot.into_public_bot(user)))
} }

View File

@@ -1,5 +1,9 @@
use revolt_quark::{models::{User, Webhook, File, Channel}, perms, Db, Error, Permission, Ref, Result}; use revolt_database::{Database, Webhook};
use rocket::serde::json::Json; use revolt_quark::{
models::{Channel, User},
perms, Db, Error, Permission, Ref, Result,
};
use rocket::{serde::json::Json, State};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use ulid::Ulid; use ulid::Ulid;
use validator::Validate; use validator::Validate;
@@ -10,46 +14,56 @@ pub struct CreateWebhookBody {
name: String, name: String,
#[validate(length(min = 1, max = 128))] #[validate(length(min = 1, max = 128))]
avatar: Option<String> avatar: Option<String>,
} }
/// # Creates a webhook /// # Creates a webhook
/// ///
/// creates a webhook which 3rd party platforms can use to send messages /// Creates a webhook which 3rd party platforms can use to send messages
#[openapi(tag = "Webhooks")] #[openapi(tag = "Webhooks")]
#[post("/<target>/webhooks", data = "<data>")] #[post("/<target>/webhooks", data = "<data>")]
pub async fn req(db: &Db, user: User, target: Ref, data: Json<CreateWebhookBody>) -> Result<Json<Webhook>> { pub async fn req(
db: &State<Database>,
legacy_db: &Db,
user: User,
target: Ref,
data: Json<CreateWebhookBody>,
) -> Result<Json<revolt_models::v0::Webhook>> {
let data = data.into_inner(); let data = data.into_inner();
data.validate() data.validate()
.map_err(|error| Error::FailedValidation { error })?; .map_err(|error| Error::FailedValidation { error })?;
let channel = target.as_channel(db).await?; let channel = target.as_channel(legacy_db).await?;
if !matches!(channel, Channel::TextChannel { .. } | Channel::Group { .. }) { if !matches!(channel, Channel::TextChannel { .. } | Channel::Group { .. }) {
return Err(Error::InvalidOperation) return Err(Error::InvalidOperation);
} }
let mut permissions = perms(&user).channel(&channel); let mut permissions = perms(&user).channel(&channel);
permissions permissions
.has_permission(db, Permission::ManageWebhooks) .has_permission(legacy_db, Permission::ManageWebhooks)
.await?; .await?;
let webhook_id = Ulid::new().to_string(); let webhook_id = Ulid::new().to_string();
let avatar = match &data.avatar { let avatar = match &data.avatar {
Some(id) => Some(File::use_avatar(db, id, &webhook_id).await?), Some(id) => Some(
None => None db.find_and_use_attachment(id, "avatars", "user", &webhook_id)
.await
.map_err(Error::from_core)?,
),
None => None,
}; };
let webhook = Webhook { let webhook = Webhook {
id: webhook_id, id: webhook_id,
name: data.name, name: data.name,
avatar, avatar,
channel: channel.id().to_string(), channel_id: channel.id().to_string(),
token: Some(nanoid::nanoid!(64)) token: Some(nanoid::nanoid!(64)),
}; };
webhook.create(db).await?; webhook.create(db).await.map_err(Error::from_core)?;
Ok(Json(webhook)) Ok(Json(webhook.into()))
} }

View File

@@ -1,19 +1,31 @@
use revolt_quark::{models::{User, Webhook}, perms, Db, Permission, Ref, Result}; use revolt_database::Database;
use rocket::serde::json::Json; use revolt_models::v0::Webhook;
use revolt_quark::{models::User, perms, Db, Error, Permission, Ref, Result};
use rocket::{serde::json::Json, State};
/// # Gets all webhooks /// # Gets all webhooks
/// ///
/// gets all webhooks inside the channel /// Gets all webhooks inside the channel
#[openapi(tag = "Webhooks")] #[openapi(tag = "Webhooks")]
#[get("/<target>/webhooks")] #[get("/<channel_id>/webhooks")]
pub async fn req(db: &Db, user: User, target: Ref) -> Result<Json<Vec<Webhook>>> { pub async fn req(
let channel = target.as_channel(db).await?; db: &State<Database>,
legacy_db: &Db,
user: User,
channel_id: Ref,
) -> Result<Json<Vec<Webhook>>> {
let channel = channel_id.as_channel(legacy_db).await?;
let mut permissions = perms(&user).channel(&channel); let mut permissions = perms(&user).channel(&channel);
permissions permissions
.has_permission(db, Permission::ManageWebhooks) .has_permission(legacy_db, Permission::ManageWebhooks)
.await?; .await?;
let webhooks = db.fetch_webhooks_for_channel(channel.id()).await?; Ok(Json(
db.fetch_webhooks_for_channel(channel.id())
Ok(Json(webhooks)) .await
.map_err(Error::from_core)?
.into_iter()
.map(|v| v.into())
.collect::<Vec<Webhook>>(),
))
} }

View File

@@ -1,21 +1,34 @@
use revolt_quark::{Db, Ref, Result, EmptyResponse, models::User, perms, Permission}; use revolt_database::Database;
use revolt_quark::{models::User, perms, Db, Error, Permission, Result};
use rocket::State;
use rocket_empty::EmptyResponse;
/// # Deletes a webhook /// # Deletes a webhook
/// ///
/// deletes a webhook /// Deletes a webhook
#[openapi(tag = "Webhooks")] #[openapi(tag = "Webhooks")]
#[delete("/<target>")] #[delete("/<webhook_id>")]
pub async fn webhook_delete(db: &Db, user: User, target: Ref) -> Result<EmptyResponse> { pub async fn webhook_delete(
let webhook = target.as_webhook(db).await?; db: &State<Database>,
legacy_db: &Db,
user: User,
webhook_id: String,
) -> Result<EmptyResponse> {
let webhook = db
.fetch_webhook(&webhook_id)
.await
.map_err(Error::from_core)?;
let channel = Ref::from_unchecked(webhook.channel.clone()).as_channel(db).await?; let channel = legacy_db.fetch_channel(&webhook.channel_id).await?;
perms(&user) perms(&user)
.channel(&channel) .channel(&channel)
.throw_permission(db, Permission::ManageWebhooks) .throw_permission(legacy_db, Permission::ManageWebhooks)
.await?; .await?;
webhook.delete(db).await?; webhook
.delete(db)
Ok(EmptyResponse) .await
.map(|_| EmptyResponse)
.map_err(Error::from_core)
} }

View File

@@ -1,18 +1,19 @@
use revolt_quark::{Db, Ref, Result, EmptyResponse, Error}; use revolt_database::Database;
use revolt_result::Result;
use rocket::State;
use rocket_empty::EmptyResponse;
/// # Deletes a webhook /// # Deletes a webhook
/// ///
/// deletes a webhook with a token /// Deletes a webhook with a token
#[openapi(tag = "Webhooks")] #[openapi(tag = "Webhooks")]
#[delete("/<target>/<token>")] #[delete("/<webhook_id>/<token>")]
pub async fn webhook_delete_token(db: &Db, target: Ref, token: String) -> Result<EmptyResponse> { pub async fn webhook_delete_token(
let webhook = target.as_webhook(db).await?; db: &State<Database>,
webhook_id: String,
(webhook.token.as_deref() == Some(&token)) token: String,
.then_some(()) ) -> Result<EmptyResponse> {
.ok_or(Error::InvalidCredentials)?; let webhook = db.fetch_webhook(&webhook_id).await?;
webhook.assert_token(&token)?;
webhook.delete(db).await?; webhook.delete(db).await.map(|_| EmptyResponse)
Ok(EmptyResponse)
} }

View File

@@ -1,60 +1,65 @@
use revolt_quark::{Db, Ref, Result, Error, models::{webhook::{FieldsWebhook, Webhook, PartialWebhook}, File, User}, Permission, perms}; use revolt_database::{Database, PartialWebhook};
use serde::{Serialize, Deserialize}; use revolt_models::v0::{DataEditWebhook, Webhook};
use revolt_quark::{models::User, perms, Db, Error, Permission, Result};
use rocket::{serde::json::Json, State};
use validator::Validate; use validator::Validate;
use rocket::serde::json::Json;
#[derive(Serialize, Deserialize, Validate, JsonSchema)]
pub struct WebhookEditBody {
#[validate(length(min = 1, max = 32))]
name: Option<String>,
#[validate(length(min = 1, max = 128))]
avatar: Option<String>,
#[serde(default)]
remove: Vec<FieldsWebhook>
}
/// # Edits a webhook /// # Edits a webhook
/// ///
/// edits a webhook /// Edits a webhook
#[openapi(tag = "Webhooks")] #[openapi(tag = "Webhooks")]
#[patch("/<target>", data="<data>")] #[patch("/<webhook_id>", data = "<data>")]
pub async fn webhook_edit(db: &Db, target: Ref, user: User, data: Json<WebhookEditBody>) -> Result<Json<Webhook>> { pub async fn webhook_edit(
db: &State<Database>,
legacy_db: &Db,
webhook_id: String,
user: User,
data: Json<DataEditWebhook>,
) -> Result<Json<Webhook>> {
let data = data.into_inner(); let data = data.into_inner();
data.validate() data.validate()
.map_err(|error| Error::FailedValidation { error })?; .map_err(|error| Error::FailedValidation { error })?;
let mut webhook = target.as_webhook(db).await?; let mut webhook = db
.fetch_webhook(&webhook_id)
.await
.map_err(Error::from_core)?;
let channel = Ref::from_unchecked(webhook.channel.clone()).as_channel(db).await?; let channel = legacy_db.fetch_channel(&webhook.channel_id).await?;
perms(&user) perms(&user)
.channel(&channel) .channel(&channel)
.throw_permission(db, Permission::ManageWebhooks) .throw_permission(legacy_db, Permission::ManageWebhooks)
.await?; .await?;
if data.name.is_none() if data.name.is_none() && data.avatar.is_none() && data.remove.is_empty() {
&& data.avatar.is_none() return Ok(Json(webhook.into()));
&& data.remove.is_empty()
{
return Ok(Json(webhook))
}; };
let mut partial = PartialWebhook::default(); let DataEditWebhook {
name,
avatar,
remove,
} = data;
let WebhookEditBody { name, avatar, remove } = data; let mut partial = PartialWebhook {
name,
if let Some(name) = name { ..Default::default()
partial.name = Some(name) };
}
if let Some(avatar) = avatar { if let Some(avatar) = avatar {
let file = File::use_avatar(db, &avatar, &webhook.id).await?; let file = db
.find_and_use_attachment(&avatar, "avatars", "user", &webhook.id)
.await
.map_err(Error::from_core)?;
partial.avatar = Some(file) partial.avatar = Some(file)
} }
webhook.update(db, partial, remove).await?; webhook
.update(db, partial, remove.into_iter().map(|v| v.into()).collect())
.await
.map_err(Error::from_core)?;
Ok(Json(webhook)) Ok(Json(webhook.into()))
} }

View File

@@ -1,57 +1,56 @@
use revolt_quark::{Db, Ref, Result, Error, models::{webhook::{FieldsWebhook, Webhook, PartialWebhook}, File}}; use revolt_database::{Database, PartialWebhook};
use serde::{Serialize, Deserialize}; use revolt_models::v0::{DataEditWebhook, Webhook};
use validator::Validate; use revolt_models::validator::Validate;
use rocket::serde::json::Json; use revolt_result::{create_error, Result};
use rocket::{serde::json::Json, State};
#[derive(Serialize, Deserialize, Validate, JsonSchema)]
pub struct WebhookEditBody {
#[validate(length(min = 1, max = 32))]
name: Option<String>,
#[validate(length(min = 1, max = 128))]
avatar: Option<String>,
#[serde(default)]
remove: Vec<FieldsWebhook>
}
/// # Edits a webhook /// # Edits a webhook
/// ///
/// edits a webhook with a token /// Edits a webhook with a token
#[openapi(tag = "Webhooks")] #[openapi(tag = "Webhooks")]
#[patch("/<target>/<token>", data="<data>")] #[patch("/<webhook_id>/<token>", data = "<data>")]
pub async fn webhook_edit_token(db: &Db, target: Ref, token: String, data: Json<WebhookEditBody>) -> Result<Json<Webhook>> { pub async fn webhook_edit_token(
db: &State<Database>,
webhook_id: String,
token: String,
data: Json<DataEditWebhook>,
) -> Result<Json<Webhook>> {
let data = data.into_inner(); let data = data.into_inner();
data.validate() data.validate().map_err(|error| {
.map_err(|error| Error::FailedValidation { error })?; create_error!(FailedValidation {
error: error.to_string()
})
})?;
let mut webhook = target.as_webhook(db).await?; let mut webhook = db.fetch_webhook(&webhook_id).await?;
webhook.assert_token(&token)?;
(webhook.token.as_deref() == Some(&token)) if data.name.is_none() && data.avatar.is_none() && data.remove.is_empty() {
.then_some(()) return Ok(Json(webhook.into()));
.ok_or(Error::InvalidCredentials)?;
if data.name.is_none()
&& data.avatar.is_none()
&& data.remove.is_empty()
{
return Ok(Json(webhook))
}; };
let mut partial = PartialWebhook::default(); let DataEditWebhook {
name,
avatar,
remove,
} = data;
let WebhookEditBody { name, avatar, remove } = data; let mut partial = PartialWebhook {
name,
if let Some(name) = name { ..Default::default()
partial.name = Some(name) };
}
if let Some(avatar) = avatar { if let Some(avatar) = avatar {
let file = File::use_avatar(db, &avatar, &webhook.id).await?; let file = db
.find_and_use_attachment(&avatar, "avatars", "user", &webhook.id)
.await?;
partial.avatar = Some(file) partial.avatar = Some(file)
} }
webhook.update(db, partial, remove).await?; webhook
.update(db, partial, remove.into_iter().map(|v| v.into()).collect())
.await?;
Ok(Json(webhook)) Ok(Json(webhook.into()))
} }

View File

@@ -1,26 +1,47 @@
use revolt_quark::{Db, Ref, Result, Error, models::message::{Message, DataMessageSend}, web::idempotency::IdempotencyKey, types::push::MessageAuthor}; use revolt_database::Database;
use rocket::serde::json::Json; use revolt_quark::{
models::message::{DataMessageSend, Message},
types::push::MessageAuthor,
web::idempotency::IdempotencyKey,
Db, Error, Result,
};
use rocket::{serde::json::Json, State};
use validator::Validate; use validator::Validate;
/// # Executes a webhook /// # Executes a webhook
/// ///
/// executes a webhook and sends a message /// Executes a webhook and sends a message
#[openapi(tag = "Webhooks")] #[openapi(tag = "Webhooks")]
#[post("/<target>/<token>", data="<data>")] #[post("/<webhook_id>/<token>", data = "<data>")]
pub async fn webhook_execute(db: &Db, target: Ref, token: String, data: Json<DataMessageSend>, idempotency: IdempotencyKey) -> Result<Json<Message>> { pub async fn webhook_execute(
db: &State<Database>,
legacy_db: &Db,
webhook_id: String,
token: String,
data: Json<DataMessageSend>,
idempotency: IdempotencyKey,
) -> Result<Json<Message>> {
let data = data.into_inner(); let data = data.into_inner();
data.validate() data.validate()
.map_err(|error| Error::FailedValidation { error })?; .map_err(|error| Error::FailedValidation { error })?;
let webhook = target.as_webhook(db).await?; let webhook = db
.fetch_webhook(&webhook_id)
.await
.map_err(Error::from_core)?;
(webhook.token.as_deref() == Some(&token)) webhook.assert_token(&token).map_err(Error::from_core)?;
.then_some(())
.ok_or(Error::InvalidCredentials)?;
let channel = Ref::from_unchecked(webhook.channel.clone()).as_channel(db).await?; let channel = legacy_db.fetch_channel(&webhook.channel_id).await?;
let message = channel.send_message(db, data, MessageAuthor::Webhook(&webhook), idempotency).await?; let message = channel
.send_message(
legacy_db,
data,
MessageAuthor::Webhook(&webhook.into()),
idempotency,
)
.await?;
Ok(Json(message)) Ok(Json(message))
} }

View File

@@ -1,6 +1,16 @@
use revolt_quark::{Db, Ref, Result, Error, models::{Message, message::SendableEmbed}, types::push::MessageAuthor}; use revolt_database::Database;
use rocket::{Request, request::FromRequest, http::Status}; use revolt_models::v0::Webhook;
use revolt_rocket_okapi::{request::{OpenApiFromRequest, RequestHeaderInput}, revolt_okapi::openapi3::{Parameter, ParameterValue, MediaType}, gen::OpenApiGenerator}; use revolt_quark::{
models::{message::SendableEmbed, Message},
types::push::MessageAuthor,
Db, Error, Result,
};
use revolt_rocket_okapi::{
gen::OpenApiGenerator,
request::{OpenApiFromRequest, RequestHeaderInput},
revolt_okapi::openapi3::{MediaType, Parameter, ParameterValue},
};
use rocket::{http::Status, request::FromRequest, Request, State};
use schemars::schema::SchemaObject; use schemars::schema::SchemaObject;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::Value; use serde_json::Value;
@@ -30,14 +40,14 @@ pub struct GithubUser {
#[derive(Serialize, Deserialize, Debug, JsonSchema)] #[derive(Serialize, Deserialize, Debug, JsonSchema)]
pub struct GithubRepositorySecurityAndAnalysisStatus { pub struct GithubRepositorySecurityAndAnalysisStatus {
status: String status: String,
} }
#[derive(Serialize, Deserialize, Debug, JsonSchema)] #[derive(Serialize, Deserialize, Debug, JsonSchema)]
pub struct GithubRepositorySecurityAndAnalysis { pub struct GithubRepositorySecurityAndAnalysis {
advanced_security: GithubRepositorySecurityAndAnalysisStatus, advanced_security: GithubRepositorySecurityAndAnalysisStatus,
secret_scanning: GithubRepositorySecurityAndAnalysisStatus, secret_scanning: GithubRepositorySecurityAndAnalysisStatus,
secret_scanning_push_protection: GithubRepositorySecurityAndAnalysisStatus secret_scanning_push_protection: GithubRepositorySecurityAndAnalysisStatus,
} }
#[derive(Serialize, Deserialize, Debug, JsonSchema)] #[derive(Serialize, Deserialize, Debug, JsonSchema)]
@@ -55,7 +65,7 @@ pub struct GithubRepositoryCodeOfConduct {
name: String, name: String,
url: String, url: String,
body: Option<String>, body: Option<String>,
html_url: Option<String> html_url: Option<String>,
} }
#[derive(Serialize, Deserialize, Debug, JsonSchema)] #[derive(Serialize, Deserialize, Debug, JsonSchema)]
@@ -64,7 +74,7 @@ pub struct GithubRepositoryPermissions {
maintain: Option<bool>, maintain: Option<bool>,
push: Option<bool>, push: Option<bool>,
triage: Option<bool>, triage: Option<bool>,
pull: Option<bool> pull: Option<bool>,
} }
#[derive(Serialize, Deserialize, Debug, JsonSchema)] #[derive(Serialize, Deserialize, Debug, JsonSchema)]
@@ -155,16 +165,15 @@ pub struct GithubRepository {
watchers: Option<u32>, watchers: Option<u32>,
allow_forking: Option<bool>, allow_forking: Option<bool>,
web_commit_signoff_required: Option<bool>, web_commit_signoff_required: Option<bool>,
security_and_analysis: Option<GithubRepositorySecurityAndAnalysis> security_and_analysis: Option<GithubRepositorySecurityAndAnalysis>,
} }
#[derive(Serialize, Deserialize, Debug, JsonSchema)] #[derive(Serialize, Deserialize, Debug, JsonSchema)]
pub struct CommitAuthor { pub struct CommitAuthor {
date: Option<String>, date: Option<String>,
email: Option<String>, email: Option<String>,
name: String, name: String,
username: Option<String> username: Option<String>,
} }
#[derive(Serialize, Deserialize, Debug, JsonSchema)] #[derive(Serialize, Deserialize, Debug, JsonSchema)]
@@ -179,13 +188,13 @@ pub struct GithubCommit {
removed: Option<Vec<String>>, removed: Option<Vec<String>>,
timestamp: String, timestamp: String,
tree_id: String, tree_id: String,
url: String url: String,
} }
#[derive(Serialize, Deserialize, Debug, JsonSchema)] #[derive(Serialize, Deserialize, Debug, JsonSchema)]
pub struct GithubReactions { pub struct GithubReactions {
#[serde(rename="+1")] #[serde(rename = "+1")]
plus_one: u32, plus_one: u32,
#[serde(rename="-1")] #[serde(rename = "-1")]
minus_one: u32, minus_one: u32,
confused: u32, confused: u32,
eyes: u32, eyes: u32,
@@ -194,7 +203,7 @@ pub struct GithubReactions {
laugh: u32, laugh: u32,
rocket: u32, rocket: u32,
total_count: u32, total_count: u32,
url: String url: String,
} }
#[derive(Serialize, Deserialize, Debug, JsonSchema)] #[derive(Serialize, Deserialize, Debug, JsonSchema)]
@@ -212,7 +221,7 @@ pub struct GithubComment {
reactions: Option<GithubReactions>, reactions: Option<GithubReactions>,
updated_at: Value, updated_at: Value,
url: String, url: String,
user: GithubUser user: GithubUser,
} }
#[derive(Serialize, Deserialize, Debug, JsonSchema)] #[derive(Serialize, Deserialize, Debug, JsonSchema)]
@@ -221,7 +230,7 @@ pub struct GithubDiscussionComment {
comment: GithubComment, comment: GithubComment,
child_comment_count: u32, child_comment_count: u32,
parent_id: Option<u32>, parent_id: Option<u32>,
repository_url: String repository_url: String,
} }
#[derive(Serialize, Deserialize, Debug, JsonSchema)] #[derive(Serialize, Deserialize, Debug, JsonSchema)]
@@ -235,7 +244,7 @@ pub struct GithubDiscussionCategory {
created_at: Value, created_at: Value,
updated_at: Value, updated_at: Value,
slug: String, slug: String,
is_answerable: bool is_answerable: bool,
} }
#[derive(Serialize, Deserialize, Debug, JsonSchema)] #[derive(Serialize, Deserialize, Debug, JsonSchema)]
@@ -243,8 +252,8 @@ pub struct GithubDiscussion {
repository_url: String, repository_url: String,
category: GithubDiscussionCategory, category: GithubDiscussionCategory,
answer_html_url: Option<String>, answer_html_url: Option<String>,
answer_chosen_at: Value, // ?? answer_chosen_at: Value, // ??
answer_chosen_by: Value, // ?? answer_chosen_by: Value, // ??
html_url: String, html_url: String,
id: u32, id: u32,
node_id: String, node_id: String,
@@ -260,7 +269,7 @@ pub struct GithubDiscussion {
active_lock_reason: Option<String>, active_lock_reason: Option<String>,
body: String, body: String,
reactions: GithubReactions, reactions: GithubReactions,
timeline_url: String timeline_url: String,
} }
#[derive(Serialize, Deserialize, Debug, JsonSchema)] #[derive(Serialize, Deserialize, Debug, JsonSchema)]
@@ -268,12 +277,12 @@ pub struct GithubDiscussion {
#[allow(clippy::large_enum_variant)] #[allow(clippy::large_enum_variant)]
pub enum GithubDiscussionEvent { pub enum GithubDiscussionEvent {
Created { Created {
discussion: GithubDiscussion discussion: GithubDiscussion,
}, },
Answered { Answered {
discussion: GithubDiscussion, discussion: GithubDiscussion,
answer: GithubDiscussionComment answer: GithubDiscussionComment,
} },
} }
#[derive(Serialize, Deserialize, Debug, JsonSchema)] #[derive(Serialize, Deserialize, Debug, JsonSchema)]
@@ -282,10 +291,10 @@ pub enum GithubDiscussionEvent {
pub enum DiscussionCommentEvent { pub enum DiscussionCommentEvent {
Created { Created {
comment: GithubDiscussionComment, comment: GithubDiscussionComment,
discussion: GithubDiscussion discussion: GithubDiscussion,
}, },
Deleted {}, Deleted {},
Edited {} Edited {},
} }
#[derive(Serialize, Deserialize, Debug, JsonSchema)] #[derive(Serialize, Deserialize, Debug, JsonSchema)]
@@ -305,7 +314,7 @@ pub struct GithubMilestone {
created_at: Value, created_at: Value,
updated_at: Value, updated_at: Value,
closed_at: Option<Value>, closed_at: Option<Value>,
due_on: Option<String> due_on: Option<String>,
} }
#[derive(Serialize, Deserialize, Debug, JsonSchema)] #[derive(Serialize, Deserialize, Debug, JsonSchema)]
@@ -322,10 +331,9 @@ pub struct GithubAppPermissions {
checks: Option<String>, checks: Option<String>,
metadata: Option<String>, metadata: Option<String>,
contents: Option<String>, contents: Option<String>,
deployments: Option<String> deployments: Option<String>,
} }
#[derive(Serialize, Deserialize, Debug, JsonSchema)] #[derive(Serialize, Deserialize, Debug, JsonSchema)]
pub struct GithubApp { pub struct GithubApp {
id: u32, id: u32,
@@ -344,7 +352,7 @@ pub struct GithubApp {
client_id: Option<String>, client_id: Option<String>,
client_secret: Option<String>, client_secret: Option<String>,
webhook_secret: Option<String>, webhook_secret: Option<String>,
pem: Option<String> pem: Option<String>,
} }
#[derive(Serialize, Deserialize, Debug, JsonSchema)] #[derive(Serialize, Deserialize, Debug, JsonSchema)]
@@ -380,7 +388,7 @@ pub struct GithubIssue {
performed_via_github_app: Option<GithubApp>, performed_via_github_app: Option<GithubApp>,
author_association: String, author_association: String,
reactions: Option<GithubReactions>, reactions: Option<GithubReactions>,
title: String title: String,
} }
#[derive(Serialize, Deserialize, Debug, JsonSchema)] #[derive(Serialize, Deserialize, Debug, JsonSchema)]
@@ -389,10 +397,10 @@ pub struct GithubIssue {
pub enum IssueCommentEvent { pub enum IssueCommentEvent {
Created { Created {
comment: GithubComment, comment: GithubComment,
issue: GithubIssue issue: GithubIssue,
}, },
Deleted {}, Deleted {},
Edited {} Edited {},
} }
#[derive(Serialize, Deserialize, Debug, JsonSchema)] #[derive(Serialize, Deserialize, Debug, JsonSchema)]
@@ -400,32 +408,26 @@ pub enum IssueCommentEvent {
#[allow(clippy::large_enum_variant)] #[allow(clippy::large_enum_variant)]
pub enum IssuesEvent { pub enum IssuesEvent {
Assigned {}, Assigned {},
Closed { Closed { issue: GithubIssue },
issue: GithubIssue
},
Deleted {}, Deleted {},
Demilestoned {}, Demilestoned {},
Edited {}, Edited {},
Labeled {}, Labeled {},
Locked {}, Locked {},
Milestoned {}, Milestoned {},
Opened { Opened { issue: GithubIssue },
issue: GithubIssue
},
Pinned {}, Pinned {},
Reopened { Reopened { issue: GithubIssue },
issue: GithubIssue
},
Transferred {}, Transferred {},
Unassigned {}, Unassigned {},
Unlabeled {}, Unlabeled {},
Unlocked {}, Unlocked {},
Unpinned {} Unpinned {},
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct StarEvent { pub struct StarEvent {
starred_at: Option<Value> starred_at: Option<Value>,
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
@@ -440,12 +442,12 @@ pub struct PushEvent {
forced: bool, forced: bool,
head_commit: Option<GithubCommit>, head_commit: Option<GithubCommit>,
pusher: CommitAuthor, pusher: CommitAuthor,
r#ref: String r#ref: String,
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct CommitCommentEvent { pub struct CommitCommentEvent {
comment: GithubComment comment: GithubComment,
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
@@ -453,19 +455,19 @@ pub struct CreateEvent {
master_branch: String, master_branch: String,
pusher_type: String, pusher_type: String,
r#ref: String, r#ref: String,
ref_type: String ref_type: String,
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct DeleteEvent { pub struct DeleteEvent {
pusher_type: String, pusher_type: String,
r#ref: String, r#ref: String,
ref_type: String ref_type: String,
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct ForkEvent { pub struct ForkEvent {
forkee: GithubRepository forkee: GithubRepository,
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
@@ -481,7 +483,7 @@ pub struct GithubTeam {
permission: String, permission: String,
members_url: String, members_url: String,
repositories_url: String, repositories_url: String,
parent: Option<Box<GithubTeam>> parent: Option<Box<GithubTeam>>,
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
@@ -490,17 +492,17 @@ pub struct GithubHead {
r#ref: String, r#ref: String,
repo: GithubRepository, repo: GithubRepository,
sha: String, sha: String,
user: Option<GithubUser> user: Option<GithubUser>,
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct GithubHref { pub struct GithubHref {
href: String href: String,
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct GithubLinks { pub struct GithubLinks {
#[serde(rename="self")] #[serde(rename = "self")]
_self: GithubHref, _self: GithubHref,
html: GithubHref, html: GithubHref,
comments: GithubHref, comments: GithubHref,
@@ -508,7 +510,7 @@ pub struct GithubLinks {
statuses: GithubHref, statuses: GithubHref,
issue: GithubHref, issue: GithubHref,
review_comments: GithubHref, review_comments: GithubHref,
review_comment: GithubHref review_comment: GithubHref,
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
@@ -516,7 +518,7 @@ pub struct GithubAutoMerge {
enabled_by: GithubUser, enabled_by: GithubUser,
merge_method: String, merge_method: String,
commit_title: String, commit_title: String,
commit_message: String commit_message: String,
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
@@ -548,7 +550,7 @@ pub struct GithubPullRequest {
_links: GithubLinks, _links: GithubLinks,
author_association: String, author_association: String,
auto_merge: Option<GithubAutoMerge>, auto_merge: Option<GithubAutoMerge>,
draft: bool draft: bool,
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
@@ -559,7 +561,7 @@ pub enum PullRequestEvent {
AutoMergeEnabled {}, AutoMergeEnabled {},
Closed { Closed {
number: u32, number: u32,
pull_request: GithubPullRequest pull_request: GithubPullRequest,
}, },
ConvertedToDraft {}, ConvertedToDraft {},
Demilestoned {}, Demilestoned {},
@@ -571,20 +573,19 @@ pub enum PullRequestEvent {
Milestoned {}, Milestoned {},
Opened { Opened {
number: u32, number: u32,
pull_request: GithubPullRequest pull_request: GithubPullRequest,
}, },
ReadyForReview {}, ReadyForReview {},
Reopened { Reopened {
number: u32, number: u32,
pull_request: GithubPullRequest pull_request: GithubPullRequest,
}, },
ReviewRequestRemoved {}, ReviewRequestRemoved {},
ReviewRequest {}, ReviewRequest {},
Synchronized {}, Synchronized {},
Unassigned {}, Unassigned {},
Unlabeled {}, Unlabeled {},
Unlocked {} Unlocked {},
} }
#[derive(Debug)] #[derive(Debug)]
@@ -601,14 +602,14 @@ pub enum BaseEvent {
Fork(ForkEvent), Fork(ForkEvent),
IssueComment(IssueCommentEvent), IssueComment(IssueCommentEvent),
Issues(IssuesEvent), Issues(IssuesEvent),
PullRequest(PullRequestEvent) PullRequest(PullRequestEvent),
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct _Event { pub struct _Event {
action: Option<String>, action: Option<String>,
sender: GithubUser, sender: GithubUser,
repository: GithubRepository repository: GithubRepository,
} }
#[derive(Debug)] #[derive(Debug)]
@@ -616,7 +617,7 @@ pub struct Event {
event: BaseEvent, event: BaseEvent,
action: Option<String>, action: Option<String>,
sender: GithubUser, sender: GithubUser,
repository: GithubRepository repository: GithubRepository,
} }
#[derive(Debug, JsonSchema)] #[derive(Debug, JsonSchema)]
@@ -634,7 +635,7 @@ impl<'r> std::ops::Deref for EventHeader<'r> {
impl<'r> FromRequest<'r> for EventHeader<'r> { impl<'r> FromRequest<'r> for EventHeader<'r> {
type Error = Error; type Error = Error;
async fn from_request(request: &'r Request<'_>) -> rocket::request::Outcome<Self,Self::Error> { async fn from_request(request: &'r Request<'_>) -> rocket::request::Outcome<Self, Self::Error> {
let headers = request.headers(); let headers = request.headers();
let Some(event) = headers.get_one("X-GitHub-Event") else { let Some(event) = headers.get_one("X-GitHub-Event") else {
return rocket::request::Outcome::Failure((Status::BadRequest, Error::InvalidOperation)) return rocket::request::Outcome::Failure((Status::BadRequest, Error::InvalidOperation))
@@ -651,29 +652,30 @@ impl<'r> OpenApiFromRequest<'r> for EventHeader<'r> {
_required: bool, _required: bool,
) -> revolt_rocket_okapi::Result<RequestHeaderInput> { ) -> revolt_rocket_okapi::Result<RequestHeaderInput> {
let mut content = schemars::Map::new(); let mut content = schemars::Map::new();
content.insert("X-Github-Event".to_string(), MediaType { content.insert(
schema: Some(SchemaObject { "X-Github-Event".to_string(),
string: Some(Box::default()), MediaType {
..Default::default() schema: Some(SchemaObject {
}), string: Some(Box::default()),
example: None, ..Default::default()
examples: None, }),
encoding: schemars::Map::new(), example: None,
extensions: schemars::Map::new(), examples: None,
}); encoding: schemars::Map::new(),
extensions: schemars::Map::new(),
},
);
Ok(RequestHeaderInput::Parameter( Ok(RequestHeaderInput::Parameter(Parameter {
Parameter { name: "X-Github-Event".to_string(),
name: "X-Github-Event".to_string(), location: "header".to_string(),
location: "header".to_string(), required: true,
required: true, description: Some("The name of the github event".to_string()),
description: Some("The name of the github event".to_string()), deprecated: false,
deprecated: false, allow_empty_value: false,
allow_empty_value: false, value: ParameterValue::Content { content },
value: ParameterValue::Content { content }, extensions: schemars::Map::new(),
extensions: schemars::Map::new() }))
}
))
} }
} }
@@ -724,53 +726,82 @@ fn convert_event(data: &str, event_name: &str) -> Result<Event> {
"issue_comment" => BaseEvent::IssueComment(safe_from_str(data)?), "issue_comment" => BaseEvent::IssueComment(safe_from_str(data)?),
"issues" => BaseEvent::Issues(safe_from_str(data)?), "issues" => BaseEvent::Issues(safe_from_str(data)?),
"pull_request" => BaseEvent::PullRequest(safe_from_str(data)?), "pull_request" => BaseEvent::PullRequest(safe_from_str(data)?),
_ => return Err(Error::InvalidOperation) _ => return Err(Error::InvalidOperation),
}; };
let _Event { action, sender, repository } = event; let _Event {
action,
sender,
repository,
} = event;
Ok(Event { Ok(Event {
action, action,
sender, sender,
repository, repository,
event: base_event event: base_event,
}) })
} }
/// # Executes a webhook specific to github /// # Executes a webhook specific to github
/// ///
/// executes a webhook specific to github and sends a message containg the relavent info about the event /// Executes a webhook specific to github and sends a message containing the relevant info about the event
#[openapi(tag = "Webhooks")] #[openapi(tag = "Webhooks")]
#[post("/<target>/<token>/github", data="<data>")] #[post("/<webhook_id>/<token>/github", data = "<data>")]
pub async fn webhook_execute_github(db: &Db, target: Ref, token: String, event: EventHeader<'_>, data: String) -> Result<()> { pub async fn webhook_execute_github(
let webhook = target.as_webhook(db).await?; db: &State<Database>,
legacy_db: &Db,
webhook_id: String,
token: String,
event: EventHeader<'_>,
data: String,
) -> Result<()> {
let webhook = db
.fetch_webhook(&webhook_id)
.await
.map_err(Error::from_core)?;
(webhook.token.as_deref() == Some(&token)) webhook.assert_token(&token).map_err(Error::from_core)?;
.then_some(())
.ok_or(Error::InvalidCredentials)?;
let channel = db.fetch_channel(&webhook.channel).await?;
let channel = legacy_db.fetch_channel(&webhook.channel_id).await?;
let event = convert_event(&data, &event)?; let event = convert_event(&data, &event)?;
let sendable_embed = match event.event { let sendable_embed = match event.event {
BaseEvent::Star(_) => { BaseEvent::Star(_) => {
if event.action.as_deref() != Some("created") { return Ok(()) }; if event.action.as_deref() != Some("created") {
return Ok(());
};
SendableEmbed { SendableEmbed {
title: Some(event.sender.login), title: Some(event.sender.login),
description: Some(format!("#### [[{}] New star added]({})", event.repository.full_name, event.repository.html_url)), description: Some(format!(
"#### [[{}] New star added]({})",
event.repository.full_name, event.repository.html_url
)),
colour: Some(GREY.to_string()), colour: Some(GREY.to_string()),
icon_url: Some(event.sender.avatar_url), icon_url: Some(event.sender.avatar_url),
url: Some(event.sender.html_url), url: Some(event.sender.html_url),
..Default::default() ..Default::default()
} }
}, }
BaseEvent::Push( PushEvent { after, commits, compare, forced, r#ref, .. }) => { BaseEvent::Push(PushEvent {
after,
commits,
compare,
forced,
r#ref,
..
}) => {
let Some(branch) = r#ref.split('/').nth(2) else { return Ok(()) }; let Some(branch) = r#ref.split('/').nth(2) else { return Ok(()) };
if forced { if forced {
let description = format!("#### [{}] Branch {} was force-pushed to {}\n[compare changes]({})", event.repository.full_name, branch, &after[0..=7], compare); let description = format!(
"#### [{}] Branch {} was force-pushed to {}\n[compare changes]({})",
event.repository.full_name,
branch,
&after[0..=7],
compare
);
SendableEmbed { SendableEmbed {
icon_url: Some(event.sender.avatar_url), icon_url: Some(event.sender.avatar_url),
@@ -781,10 +812,24 @@ pub async fn webhook_execute_github(db: &Db, target: Ref, token: String, event:
..Default::default() ..Default::default()
} }
} else { } else {
let title = format!("[[{}:{}] {} new commit]({})", event.repository.full_name, branch, commits.len(), compare); let title = format!(
"[[{}:{}] {} new commit]({})",
event.repository.full_name,
branch,
commits.len(),
compare
);
let commit_description = commits let commit_description = commits
.into_iter() .into_iter()
.map(|commit| format!("[`{}`]({}) {} - {}", &commit.id[0..=7], commit.url, shorten_text(&commit.message, 50), commit.author.name)) .map(|commit| {
format!(
"[`{}`]({}) {} - {}",
&commit.id[0..=7],
commit.url,
shorten_text(&commit.message, 50),
commit.author.name
)
})
.collect::<Vec<String>>() .collect::<Vec<String>>()
.join("\n"); .join("\n");
@@ -797,193 +842,245 @@ pub async fn webhook_execute_github(db: &Db, target: Ref, token: String, event:
..Default::default() ..Default::default()
} }
} }
}, }
BaseEvent::CommitComment(CommitCommentEvent { comment }) => { BaseEvent::CommitComment(CommitCommentEvent { comment }) => {
let commit_id = match comment.commit_id { let commit_id = match comment.commit_id {
Some(id) => id[0..=7].to_string(), Some(id) => id[0..=7].to_string(),
None => "".to_string() None => "".to_string(),
}; };
SendableEmbed { SendableEmbed {
icon_url: Some(event.sender.avatar_url), icon_url: Some(event.sender.avatar_url),
url: Some(event.sender.html_url), url: Some(event.sender.html_url),
title: Some(event.sender.login), title: Some(event.sender.login),
description: Some(format!("#### [[{}] New comment on commit `{}`]({})\n{}", event.repository.full_name, commit_id, comment.html_url, shorten_text(&comment.body, 450))), description: Some(format!(
"#### [[{}] New comment on commit `{}`]({})\n{}",
event.repository.full_name,
commit_id,
comment.html_url,
shorten_text(&comment.body, 450)
)),
colour: Some(GREY.to_string()), colour: Some(GREY.to_string()),
..Default::default() ..Default::default()
} }
},
BaseEvent::Ping => {
return Ok(())
},
BaseEvent::Create(CreateEvent { r#ref, ref_type, .. }) => {
SendableEmbed {
icon_url: Some(event.sender.avatar_url),
url: Some(event.sender.html_url),
title: Some(event.sender.login),
description: Some(format!("#### [{}] New {} created: {}", event.repository.full_name, ref_type, r#ref)),
colour: Some(GREY.to_string()),
..Default::default()
}
},
BaseEvent::Delete( DeleteEvent { r#ref, ref_type, .. }) => {
SendableEmbed {
icon_url: Some(event.sender.avatar_url),
url: Some(event.sender.html_url),
title: Some(event.sender.login),
description: Some(format!("#### [{}] {} deleted: {}", event.repository.full_name, ref_type, r#ref)),
colour: Some(GREY.to_string()),
..Default::default()
}
},
BaseEvent::Discussion(discussion_event) => {
match discussion_event {
GithubDiscussionEvent::Created { discussion } => {
SendableEmbed {
icon_url: Some(event.sender.avatar_url),
url: Some(event.sender.html_url),
title: Some(event.sender.login),
description: Some(format!("#### [{}] New discussion #{}: {}\n{}", event.repository.full_name, discussion.number, discussion.title, shorten_text(&discussion.body, 450))),
colour: Some(LIGHT_ORANGE.to_string()),
..Default::default()
}
},
GithubDiscussionEvent::Answered { discussion, answer } => {
SendableEmbed {
icon_url: Some(answer.comment.user.avatar_url),
url: Some(answer.comment.user.html_url),
title: Some(answer.comment.user.login),
description: Some(format!("#### [{}] discussion #{} marked answered: {}\n{}", event.repository.full_name, discussion.number, discussion.title, shorten_text(&answer.comment.body, 450))),
colour: Some(LIGHT_ORANGE.to_string()),
..Default::default()
}
},
}
},
BaseEvent::DiscussionComment(comment_event) => {
match comment_event {
DiscussionCommentEvent::Created { comment, discussion } => {
SendableEmbed {
icon_url: Some(comment.comment.user.avatar_url),
url: Some(comment.comment.user.html_url),
title: Some(comment.comment.user.login),
description: Some(format!("[{}] New comment on discussion #{}: {}\n{}", event.repository.full_name, discussion.number, discussion.title, shorten_text(&comment.comment.body, 450))),
colour: Some(LIGHT_ORANGE.to_string()),
..Default::default()
}
},
_ => { return Ok(()) }
}
},
BaseEvent::Fork(ForkEvent { forkee }) => {
SendableEmbed {
icon_url: Some(event.sender.avatar_url),
url: Some(event.sender.html_url),
title: Some(event.sender.login),
description: Some(format!("#### [[{}] Fork created: {}]({})", event.repository.full_name, forkee.full_name, forkee.html_url)),
colour: Some(GREY.to_string()),
..Default::default()
}
},
BaseEvent::Issues(issue_event) => {
match issue_event {
IssuesEvent::Closed { issue } => {
SendableEmbed {
icon_url: Some(event.sender.avatar_url),
url: Some(event.sender.html_url),
title: Some(event.sender.login),
description: Some(format!("#### [[{}] Issue Closed #{}: {}]({})", event.repository.full_name, issue.number, issue.title, issue.html_url)),
colour: Some(GREY.to_string()),
..Default::default()
}
},
IssuesEvent::Opened { issue } => {
SendableEmbed {
icon_url: Some(event.sender.avatar_url),
url: Some(event.sender.html_url),
title: Some(event.sender.login),
description: Some(format!("#### [[{}] Issue Opened #{}: {}]({})\n{}", event.repository.full_name, issue.number, issue.title, issue.html_url, shorten_text(&issue.body.unwrap_or_default(), 450))),
colour: Some(ORANGE.to_string()),
..Default::default()
}
},
IssuesEvent::Reopened { issue } => {
SendableEmbed {
icon_url: Some(event.sender.avatar_url),
url: Some(event.sender.html_url),
title: Some(event.sender.login),
description: Some(format!("#### [[{}] Issue Reopened #{}: {}]({})", event.repository.full_name, issue.number, issue.html_url, issue.title)),
colour: Some(GREEN.to_string()),
..Default::default()
}
},
_ => { return Ok(()) }
}
},
BaseEvent::IssueComment(comment_event) => {
match comment_event {
IssueCommentEvent::Created { comment, issue } => {
SendableEmbed {
icon_url: Some(event.sender.avatar_url),
url: Some(event.sender.html_url),
title: Some(event.sender.login),
description: Some(format!("#### [[{}] New comment on issue #{}: {}]({})\n{}", event.repository.full_name, issue.number, issue.title, issue.html_url, shorten_text(&comment.body, 450))),
colour: Some(LIGHT_ORANGE.to_string()),
..Default::default()
}
},
_ => { return Ok(()) }
}
},
BaseEvent::PullRequest(pull_request_event) => {
match pull_request_event {
PullRequestEvent::Closed { number, pull_request } => {
SendableEmbed {
icon_url: Some(event.sender.avatar_url),
url: Some(event.sender.html_url),
title: Some(event.sender.login),
description: Some(format!("#### [[{}] Pull Request Closed #{}: {}]({})", event.repository.full_name, number, pull_request.title, pull_request.html_url)),
colour: Some(GREY.to_string()),
..Default::default()
}
},
PullRequestEvent::Opened { number, pull_request } => {
SendableEmbed {
icon_url: Some(event.sender.avatar_url),
url: Some(event.sender.html_url),
title: Some(event.sender.login),
description: Some(format!("#### [[{}] Pull Request Opened #{}: {}]({})\n{}", event.repository.full_name, number, pull_request.title, pull_request.html_url, shorten_text(&pull_request.body.unwrap_or_default(), 450))),
colour: Some(ORANGE.to_string()),
..Default::default()
}
},
PullRequestEvent::Reopened { number, pull_request } => {
SendableEmbed {
icon_url: Some(event.sender.avatar_url),
url: Some(event.sender.html_url),
title: Some(event.sender.login),
description: Some(format!("#### [[{}] Pull Request Reopened #{}: {}]({})", event.repository.full_name, number, pull_request.html_url, pull_request.title)),
colour: Some(GREEN.to_string()),
..Default::default()
}
},
_ => { return Ok(()) }
}
} }
BaseEvent::Ping => return Ok(()),
BaseEvent::Create(CreateEvent {
r#ref, ref_type, ..
}) => SendableEmbed {
icon_url: Some(event.sender.avatar_url),
url: Some(event.sender.html_url),
title: Some(event.sender.login),
description: Some(format!(
"#### [{}] New {} created: {}",
event.repository.full_name, ref_type, r#ref
)),
colour: Some(GREY.to_string()),
..Default::default()
},
BaseEvent::Delete(DeleteEvent {
r#ref, ref_type, ..
}) => SendableEmbed {
icon_url: Some(event.sender.avatar_url),
url: Some(event.sender.html_url),
title: Some(event.sender.login),
description: Some(format!(
"#### [{}] {} deleted: {}",
event.repository.full_name, ref_type, r#ref
)),
colour: Some(GREY.to_string()),
..Default::default()
},
BaseEvent::Discussion(discussion_event) => match discussion_event {
GithubDiscussionEvent::Created { discussion } => SendableEmbed {
icon_url: Some(event.sender.avatar_url),
url: Some(event.sender.html_url),
title: Some(event.sender.login),
description: Some(format!(
"#### [{}] New discussion #{}: {}\n{}",
event.repository.full_name,
discussion.number,
discussion.title,
shorten_text(&discussion.body, 450)
)),
colour: Some(LIGHT_ORANGE.to_string()),
..Default::default()
},
GithubDiscussionEvent::Answered { discussion, answer } => SendableEmbed {
icon_url: Some(answer.comment.user.avatar_url),
url: Some(answer.comment.user.html_url),
title: Some(answer.comment.user.login),
description: Some(format!(
"#### [{}] discussion #{} marked answered: {}\n{}",
event.repository.full_name,
discussion.number,
discussion.title,
shorten_text(&answer.comment.body, 450)
)),
colour: Some(LIGHT_ORANGE.to_string()),
..Default::default()
},
},
BaseEvent::DiscussionComment(comment_event) => match comment_event {
DiscussionCommentEvent::Created {
comment,
discussion,
} => SendableEmbed {
icon_url: Some(comment.comment.user.avatar_url),
url: Some(comment.comment.user.html_url),
title: Some(comment.comment.user.login),
description: Some(format!(
"[{}] New comment on discussion #{}: {}\n{}",
event.repository.full_name,
discussion.number,
discussion.title,
shorten_text(&comment.comment.body, 450)
)),
colour: Some(LIGHT_ORANGE.to_string()),
..Default::default()
},
_ => return Ok(()),
},
BaseEvent::Fork(ForkEvent { forkee }) => SendableEmbed {
icon_url: Some(event.sender.avatar_url),
url: Some(event.sender.html_url),
title: Some(event.sender.login),
description: Some(format!(
"#### [[{}] Fork created: {}]({})",
event.repository.full_name, forkee.full_name, forkee.html_url
)),
colour: Some(GREY.to_string()),
..Default::default()
},
BaseEvent::Issues(issue_event) => match issue_event {
IssuesEvent::Closed { issue } => SendableEmbed {
icon_url: Some(event.sender.avatar_url),
url: Some(event.sender.html_url),
title: Some(event.sender.login),
description: Some(format!(
"#### [[{}] Issue Closed #{}: {}]({})",
event.repository.full_name, issue.number, issue.title, issue.html_url
)),
colour: Some(GREY.to_string()),
..Default::default()
},
IssuesEvent::Opened { issue } => SendableEmbed {
icon_url: Some(event.sender.avatar_url),
url: Some(event.sender.html_url),
title: Some(event.sender.login),
description: Some(format!(
"#### [[{}] Issue Opened #{}: {}]({})\n{}",
event.repository.full_name,
issue.number,
issue.title,
issue.html_url,
shorten_text(&issue.body.unwrap_or_default(), 450)
)),
colour: Some(ORANGE.to_string()),
..Default::default()
},
IssuesEvent::Reopened { issue } => SendableEmbed {
icon_url: Some(event.sender.avatar_url),
url: Some(event.sender.html_url),
title: Some(event.sender.login),
description: Some(format!(
"#### [[{}] Issue Reopened #{}: {}]({})",
event.repository.full_name, issue.number, issue.html_url, issue.title
)),
colour: Some(GREEN.to_string()),
..Default::default()
},
_ => return Ok(()),
},
BaseEvent::IssueComment(comment_event) => match comment_event {
IssueCommentEvent::Created { comment, issue } => SendableEmbed {
icon_url: Some(event.sender.avatar_url),
url: Some(event.sender.html_url),
title: Some(event.sender.login),
description: Some(format!(
"#### [[{}] New comment on issue #{}: {}]({})\n{}",
event.repository.full_name,
issue.number,
issue.title,
issue.html_url,
shorten_text(&comment.body, 450)
)),
colour: Some(LIGHT_ORANGE.to_string()),
..Default::default()
},
_ => return Ok(()),
},
BaseEvent::PullRequest(pull_request_event) => match pull_request_event {
PullRequestEvent::Closed {
number,
pull_request,
} => SendableEmbed {
icon_url: Some(event.sender.avatar_url),
url: Some(event.sender.html_url),
title: Some(event.sender.login),
description: Some(format!(
"#### [[{}] Pull Request Closed #{}: {}]({})",
event.repository.full_name, number, pull_request.title, pull_request.html_url
)),
colour: Some(GREY.to_string()),
..Default::default()
},
PullRequestEvent::Opened {
number,
pull_request,
} => SendableEmbed {
icon_url: Some(event.sender.avatar_url),
url: Some(event.sender.html_url),
title: Some(event.sender.login),
description: Some(format!(
"#### [[{}] Pull Request Opened #{}: {}]({})\n{}",
event.repository.full_name,
number,
pull_request.title,
pull_request.html_url,
shorten_text(&pull_request.body.unwrap_or_default(), 450)
)),
colour: Some(ORANGE.to_string()),
..Default::default()
},
PullRequestEvent::Reopened {
number,
pull_request,
} => SendableEmbed {
icon_url: Some(event.sender.avatar_url),
url: Some(event.sender.html_url),
title: Some(event.sender.login),
description: Some(format!(
"#### [[{}] Pull Request Reopened #{}: {}]({})",
event.repository.full_name, number, pull_request.html_url, pull_request.title
)),
colour: Some(GREEN.to_string()),
..Default::default()
},
_ => return Ok(()),
},
}; };
let message_id = Ulid::new().to_string(); let message_id = Ulid::new().to_string();
let embed = sendable_embed.into_embed(db, message_id.clone()).await?; let embed = sendable_embed
.into_embed(legacy_db, message_id.clone())
.await?;
let mut message = Message { let mut message = Message {
id: message_id, id: message_id,
author: webhook.id.clone(), author: webhook.id.clone(),
channel: webhook.channel.clone(), channel: webhook.channel_id.clone(),
embeds: Some(vec![embed]), embeds: Some(vec![embed]),
webhook: Some(webhook.clone().into_message_webhook()), webhook: Some(std::convert::Into::<Webhook>::into(webhook.clone()).into()),
..Default::default() ..Default::default()
}; };
message.create(db, &channel, Some(MessageAuthor::Webhook(&webhook))).await message
.create(
legacy_db,
&channel,
Some(MessageAuthor::Webhook(&webhook.into())),
)
.await
} }

View File

@@ -1,28 +1,30 @@
use revolt_quark::{Db, Ref, Result, models::{Webhook, File}}; use revolt_database::Database;
use rocket::serde::json::Json; use revolt_models::v0::{ResponseWebhook, Webhook};
use serde::{Serialize, Deserialize}; use revolt_quark::{models::User, perms, Db, Error, Permission, Result};
use rocket::{serde::json::Json, State};
// This route is used to get the info about the webhook by clients to get the name and avatar,
// so this function cant return the token or require any permissions.
#[derive(Serialize, Deserialize, Debug, JsonSchema)]
pub struct WebhookData {
#[serde(rename = "_id")]
pub id: String,
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub avatar: Option<File>,
pub channel: String,
}
/// # Gets a webhook /// # Gets a webhook
/// ///
/// gets a webhook /// Gets a webhook
#[openapi(tag = "Webhooks")] #[openapi(tag = "Webhooks")]
#[get("/<target>")] #[get("/<webhook_id>")]
pub async fn webhook_fetch(db: &Db, target: Ref) -> Result<Json<WebhookData>> { pub async fn webhook_fetch(
let Webhook { id, name, avatar, channel, .. } = target.as_webhook(db).await?; db: &State<Database>,
legacy_db: &Db,
webhook_id: String,
user: User,
) -> Result<Json<ResponseWebhook>> {
let webhook = db
.fetch_webhook(&webhook_id)
.await
.map_err(Error::from_core)?;
Ok(Json(WebhookData { id, name, avatar, channel })) let channel = legacy_db.fetch_channel(&webhook.channel_id).await?;
perms(&user)
.channel(&channel)
.throw_permission(legacy_db, Permission::ViewChannel)
.await?;
Ok(Json(std::convert::Into::<Webhook>::into(webhook).into()))
} }

View File

@@ -1,17 +1,19 @@
use revolt_quark::{Db, Ref, Result, Error, models::Webhook}; use revolt_database::Database;
use rocket::serde::json::Json; use revolt_models::v0::Webhook;
use revolt_result::Result;
use rocket::{serde::json::Json, State};
/// # Gets a webhook /// # Gets a webhook
/// ///
/// gets a webhook with a token /// Gets a webhook with a token
#[openapi(tag = "Webhooks")] #[openapi(tag = "Webhooks")]
#[get("/<target>/<token>")] #[get("/<webhook_id>/<token>")]
pub async fn webhook_fetch_token(db: &Db, target: Ref, token: String) -> Result<Json<Webhook>> { pub async fn webhook_fetch_token(
let webhook = target.as_webhook(db).await?; db: &State<Database>,
webhook_id: String,
(webhook.token.as_deref() == Some(&token)) token: String,
.then_some(()) ) -> Result<Json<Webhook>> {
.ok_or(Error::InvalidCredentials)?; let webhook = db.fetch_webhook(&webhook_id).await?;
webhook.assert_token(&token)?;
Ok(Json(webhook)) Ok(Json(webhook.into()))
} }