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

View File

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

View File

@@ -23,11 +23,12 @@ pub async fn fetch_bot(
}
Ok(Json(FetchBotResponse {
user: revolt_models::v0::User::from(
db.fetch_user(&bot.id).await.map_err(Error::from_core)?,
None,
)
.await,
user: db
.fetch_user(&bot.id)
.await
.map_err(Error::from_core)?
.into(None)
.await,
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)?;
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 rocket::serde::json::Json;
use revolt_database::{Database, Webhook};
use revolt_quark::{
models::{Channel, User},
perms, Db, Error, Permission, Ref, Result,
};
use rocket::{serde::json::Json, State};
use serde::{Deserialize, Serialize};
use ulid::Ulid;
use validator::Validate;
@@ -10,46 +14,56 @@ pub struct CreateWebhookBody {
name: String,
#[validate(length(min = 1, max = 128))]
avatar: Option<String>
avatar: Option<String>,
}
/// # 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")]
#[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();
data.validate()
.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 { .. }) {
return Err(Error::InvalidOperation)
return Err(Error::InvalidOperation);
}
let mut permissions = perms(&user).channel(&channel);
permissions
.has_permission(db, Permission::ManageWebhooks)
.has_permission(legacy_db, Permission::ManageWebhooks)
.await?;
let webhook_id = Ulid::new().to_string();
let avatar = match &data.avatar {
Some(id) => Some(File::use_avatar(db, id, &webhook_id).await?),
None => None
Some(id) => Some(
db.find_and_use_attachment(id, "avatars", "user", &webhook_id)
.await
.map_err(Error::from_core)?,
),
None => None,
};
let webhook = Webhook {
id: webhook_id,
name: data.name,
avatar,
channel: channel.id().to_string(),
token: Some(nanoid::nanoid!(64))
channel_id: channel.id().to_string(),
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 rocket::serde::json::Json;
use revolt_database::Database;
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 inside the channel
/// Gets all webhooks inside the channel
#[openapi(tag = "Webhooks")]
#[get("/<target>/webhooks")]
pub async fn req(db: &Db, user: User, target: Ref) -> Result<Json<Vec<Webhook>>> {
let channel = target.as_channel(db).await?;
#[get("/<channel_id>/webhooks")]
pub async fn req(
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);
permissions
.has_permission(db, Permission::ManageWebhooks)
.has_permission(legacy_db, Permission::ManageWebhooks)
.await?;
let webhooks = db.fetch_webhooks_for_channel(channel.id()).await?;
Ok(Json(webhooks))
Ok(Json(
db.fetch_webhooks_for_channel(channel.id())
.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
#[openapi(tag = "Webhooks")]
#[delete("/<target>")]
pub async fn webhook_delete(db: &Db, user: User, target: Ref) -> Result<EmptyResponse> {
let webhook = target.as_webhook(db).await?;
#[delete("/<webhook_id>")]
pub async fn webhook_delete(
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)
.channel(&channel)
.throw_permission(db, Permission::ManageWebhooks)
.throw_permission(legacy_db, Permission::ManageWebhooks)
.await?;
webhook.delete(db).await?;
Ok(EmptyResponse)
webhook
.delete(db)
.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 with a token
/// Deletes a webhook with a token
#[openapi(tag = "Webhooks")]
#[delete("/<target>/<token>")]
pub async fn webhook_delete_token(db: &Db, target: Ref, token: String) -> Result<EmptyResponse> {
let webhook = target.as_webhook(db).await?;
(webhook.token.as_deref() == Some(&token))
.then_some(())
.ok_or(Error::InvalidCredentials)?;
webhook.delete(db).await?;
Ok(EmptyResponse)
#[delete("/<webhook_id>/<token>")]
pub async fn webhook_delete_token(
db: &State<Database>,
webhook_id: String,
token: String,
) -> Result<EmptyResponse> {
let webhook = db.fetch_webhook(&webhook_id).await?;
webhook.assert_token(&token)?;
webhook.delete(db).await.map(|_| EmptyResponse)
}

View File

@@ -1,60 +1,65 @@
use revolt_quark::{Db, Ref, Result, Error, models::{webhook::{FieldsWebhook, Webhook, PartialWebhook}, File, User}, Permission, perms};
use serde::{Serialize, Deserialize};
use revolt_database::{Database, PartialWebhook};
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 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
#[openapi(tag = "Webhooks")]
#[patch("/<target>", data="<data>")]
pub async fn webhook_edit(db: &Db, target: Ref, user: User, data: Json<WebhookEditBody>) -> Result<Json<Webhook>> {
#[patch("/<webhook_id>", data = "<data>")]
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();
data.validate()
.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)
.channel(&channel)
.throw_permission(db, Permission::ManageWebhooks)
.throw_permission(legacy_db, Permission::ManageWebhooks)
.await?;
if data.name.is_none()
&& data.avatar.is_none()
&& data.remove.is_empty()
{
return Ok(Json(webhook))
if data.name.is_none() && data.avatar.is_none() && data.remove.is_empty() {
return Ok(Json(webhook.into()));
};
let mut partial = PartialWebhook::default();
let DataEditWebhook {
name,
avatar,
remove,
} = data;
let WebhookEditBody { name, avatar, remove } = data;
if let Some(name) = name {
partial.name = Some(name)
}
let mut partial = PartialWebhook {
name,
..Default::default()
};
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)
}
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 serde::{Serialize, Deserialize};
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>
}
use revolt_database::{Database, PartialWebhook};
use revolt_models::v0::{DataEditWebhook, Webhook};
use revolt_models::validator::Validate;
use revolt_result::{create_error, Result};
use rocket::{serde::json::Json, State};
/// # Edits a webhook
///
/// edits a webhook with a token
/// Edits a webhook with a token
#[openapi(tag = "Webhooks")]
#[patch("/<target>/<token>", data="<data>")]
pub async fn webhook_edit_token(db: &Db, target: Ref, token: String, data: Json<WebhookEditBody>) -> Result<Json<Webhook>> {
#[patch("/<webhook_id>/<token>", data = "<data>")]
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();
data.validate()
.map_err(|error| Error::FailedValidation { error })?;
data.validate().map_err(|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))
.then_some(())
.ok_or(Error::InvalidCredentials)?;
if data.name.is_none()
&& data.avatar.is_none()
&& data.remove.is_empty()
{
return Ok(Json(webhook))
if data.name.is_none() && data.avatar.is_none() && data.remove.is_empty() {
return Ok(Json(webhook.into()));
};
let mut partial = PartialWebhook::default();
let DataEditWebhook {
name,
avatar,
remove,
} = data;
let WebhookEditBody { name, avatar, remove } = data;
if let Some(name) = name {
partial.name = Some(name)
}
let mut partial = PartialWebhook {
name,
..Default::default()
};
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)
}
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 rocket::serde::json::Json;
use revolt_database::Database;
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;
/// # Executes a webhook
///
/// executes a webhook and sends a message
/// Executes a webhook and sends a message
#[openapi(tag = "Webhooks")]
#[post("/<target>/<token>", data="<data>")]
pub async fn webhook_execute(db: &Db, target: Ref, token: String, data: Json<DataMessageSend>, idempotency: IdempotencyKey) -> Result<Json<Message>> {
#[post("/<webhook_id>/<token>", data = "<data>")]
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();
data.validate()
.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))
.then_some(())
.ok_or(Error::InvalidCredentials)?;
webhook.assert_token(&token).map_err(Error::from_core)?;
let channel = Ref::from_unchecked(webhook.channel.clone()).as_channel(db).await?;
let message = channel.send_message(db, data, MessageAuthor::Webhook(&webhook), idempotency).await?;
let channel = legacy_db.fetch_channel(&webhook.channel_id).await?;
let message = channel
.send_message(
legacy_db,
data,
MessageAuthor::Webhook(&webhook.into()),
idempotency,
)
.await?;
Ok(Json(message))
}

View File

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