From f12ea058ded197996bd8fd2e96b666ec217a148a Mon Sep 17 00:00:00 2001 From: sychic <47618543+Sychic@users.noreply.github.com> Date: Tue, 26 May 2026 14:31:19 -0400 Subject: [PATCH] feat: revoke notification endpoint --- ...87f82c5730d835ae1c52d5df23b5f09cd1925.json | 22 +++++++++++++++ .../src/database/models/notification_item.rs | 27 +++++++++++++++++++ .../routes/internal/external_notifications.rs | 26 ++++++++++++++++-- 3 files changed, 73 insertions(+), 2 deletions(-) create mode 100644 apps/labrinth/.sqlx/query-00d809d1dc9dd1eb2c49ebaac2387f82c5730d835ae1c52d5df23b5f09cd1925.json diff --git a/apps/labrinth/.sqlx/query-00d809d1dc9dd1eb2c49ebaac2387f82c5730d835ae1c52d5df23b5f09cd1925.json b/apps/labrinth/.sqlx/query-00d809d1dc9dd1eb2c49ebaac2387f82c5730d835ae1c52d5df23b5f09cd1925.json new file mode 100644 index 0000000000..810f9c5ebb --- /dev/null +++ b/apps/labrinth/.sqlx/query-00d809d1dc9dd1eb2c49ebaac2387f82c5730d835ae1c52d5df23b5f09cd1925.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT id\n FROM notifications\n WHERE body @> $1::jsonb\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [ + "Jsonb" + ] + }, + "nullable": [ + false + ] + }, + "hash": "00d809d1dc9dd1eb2c49ebaac2387f82c5730d835ae1c52d5df23b5f09cd1925" +} diff --git a/apps/labrinth/src/database/models/notification_item.rs b/apps/labrinth/src/database/models/notification_item.rs index 0e73465377..9cc25c053c 100644 --- a/apps/labrinth/src/database/models/notification_item.rs +++ b/apps/labrinth/src/database/models/notification_item.rs @@ -597,6 +597,33 @@ impl DBNotification { Ok(Some(())) } + pub async fn remove_many_matching_body( + body_filter: &serde_json::Value, + transaction: &mut PgTransaction<'_>, + redis: &RedisPool, + ) -> Result { + let ids = sqlx::query!( + " + SELECT id + FROM notifications + WHERE body @> $1::jsonb + ", + body_filter + ) + .fetch(&mut *transaction) + .map_ok(|x| DBNotificationId(x.id)) + .try_collect::>() + .await?; + + if ids.is_empty() { + return Ok(0); + } + + Self::remove_many(&ids, transaction, redis).await?; + + Ok(ids.len()) + } + pub async fn clear_user_notifications_cache( user_ids: impl IntoIterator, redis: &RedisPool, diff --git a/apps/labrinth/src/routes/internal/external_notifications.rs b/apps/labrinth/src/routes/internal/external_notifications.rs index 9c870cf4f0..3c53655a4a 100644 --- a/apps/labrinth/src/routes/internal/external_notifications.rs +++ b/apps/labrinth/src/routes/internal/external_notifications.rs @@ -1,12 +1,13 @@ use crate::auth::get_user_from_headers; use crate::database::PgPool; use crate::database::models::ids::DBUserId; +use crate::database::models::notification_item::DBNotification; use crate::database::models::notification_item::NotificationBuilder; use crate::database::models::user_item::DBUser; use crate::database::redis::RedisPool; use crate::models::users::Role; use crate::models::v3::notifications::{ - NotificationBody, NotificationDeliveryStatus, + NotificationBody, NotificationDeliveryStatus, NotificationType, }; use crate::models::v3::pats::Scopes; use crate::queue::email::EmailQueue; @@ -16,7 +17,7 @@ use crate::util::guards::external_notification_key_guard; use actix_web::http::StatusCode; use actix_web::web; use actix_web::{ - CustomizeResponder, HttpRequest, HttpResponse, Responder, post, + CustomizeResponder, HttpRequest, HttpResponse, Responder, delete, post, }; use ariadne::ids::UserId; use eyre::eyre; @@ -26,6 +27,7 @@ use serde::Deserialize; pub fn config(cfg: &mut web::ServiceConfig) { cfg.service(create) .service(create_direct_email) + .service(remove) .service(send_custom_email); } @@ -158,6 +160,26 @@ pub async fn create_direct_email( Ok(web::Json(failed).customize().with_status(status)) } +#[delete("external_notifications", guard = "external_notification_key_guard")] +pub async fn remove( + pool: web::Data, + redis: web::Data, + notification_filter: web::Json, +) -> Result { + let mut txn = pool.begin().await?; + + DBNotification::remove_many_matching_body( + ¬ification_filter, + &mut txn, + &redis, + ) + .await?; + + txn.commit().await?; + + Ok(HttpResponse::NoContent().finish()) +} + #[derive(Deserialize)] struct SendEmail { pub users: Vec,