From 6d7b38f4dead0f268940a1be0ab7b2be71a50d58 Mon Sep 17 00:00:00 2001 From: Nutomic Date: Wed, 11 Oct 2023 16:47:22 +0200 Subject: [PATCH 01/24] Implement user data import/export (#3976) * Implement endpoints for user data import/export * add test * exclude avatar/banner * increase import url count, add rate limit * also export/import saved posts * rate limit * rename * saved posts also exist * rename routes * fix test * error handling * clippy * limit parallelism * clippy --------- Co-authored-by: Dessalines --- crates/api/src/local_user/save_settings.rs | 1 - crates/api_common/src/utils.rs | 2 + crates/apub/src/api/mod.rs | 1 + crates/apub/src/api/user_settings_backup.rs | 449 ++++++++++++++++++ crates/db_schema/src/impls/local_user.rs | 76 ++- crates/db_schema/src/schema.rs | 3 +- .../src/source/local_site_rate_limit.rs | 6 + crates/db_schema/src/source/local_user.rs | 4 - .../src/registration_application_view.rs | 1 - crates/utils/src/error.rs | 1 + crates/utils/src/rate_limit/mod.rs | 15 + crates/utils/src/rate_limit/rate_limiter.rs | 1 + .../down.sql | 3 + .../up.sql | 4 + .../down.sql | 6 + .../up.sql | 6 + src/api_routes_http.rs | 7 + 17 files changed, 577 insertions(+), 9 deletions(-) create mode 100644 crates/apub/src/api/user_settings_backup.rs create mode 100644 migrations/2023-09-20-110614_drop-show-new-post-notifs/down.sql create mode 100644 migrations/2023-09-20-110614_drop-show-new-post-notifs/up.sql create mode 100644 migrations/2023-09-28-084231_import_user_settings_rate_limit/down.sql create mode 100644 migrations/2023-09-28-084231_import_user_settings_rate_limit/up.sql diff --git a/crates/api/src/local_user/save_settings.rs b/crates/api/src/local_user/save_settings.rs index a88dc431c..c3d7eca23 100644 --- a/crates/api/src/local_user/save_settings.rs +++ b/crates/api/src/local_user/save_settings.rs @@ -106,7 +106,6 @@ pub async fn save_user_settings( email, show_avatars: data.show_avatars, show_read_posts: data.show_read_posts, - show_new_post_notifs: data.show_new_post_notifs, send_notifications_to_email: data.send_notifications_to_email, show_nsfw: data.show_nsfw, blur_nsfw: data.blur_nsfw, diff --git a/crates/api_common/src/utils.rs b/crates/api_common/src/utils.rs index 8f49eb78a..891a35855 100644 --- a/crates/api_common/src/utils.rs +++ b/crates/api_common/src/utils.rs @@ -375,6 +375,8 @@ pub fn local_site_rate_limit_to_rate_limit_config( comment_per_second: l.comment_per_second, search: l.search, search_per_second: l.search_per_second, + import_user_settings: l.import_user_settings, + import_user_settings_per_second: l.import_user_settings_per_second, } } diff --git a/crates/apub/src/api/mod.rs b/crates/apub/src/api/mod.rs index 705e81a30..59586e477 100644 --- a/crates/apub/src/api/mod.rs +++ b/crates/apub/src/api/mod.rs @@ -7,6 +7,7 @@ pub mod read_community; pub mod read_person; pub mod resolve_object; pub mod search; +pub mod user_settings_backup; /// Returns default listing type, depending if the query is for frontpage or community. fn listing_type_with_default( diff --git a/crates/apub/src/api/user_settings_backup.rs b/crates/apub/src/api/user_settings_backup.rs new file mode 100644 index 000000000..0349171a9 --- /dev/null +++ b/crates/apub/src/api/user_settings_backup.rs @@ -0,0 +1,449 @@ +use crate::objects::{ + comment::ApubComment, + community::ApubCommunity, + person::ApubPerson, + post::ApubPost, +}; +use activitypub_federation::{config::Data, fetch::object_id::ObjectId}; +use actix_web::web::Json; +use futures::{future::try_join_all, StreamExt}; +use lemmy_api_common::{context::LemmyContext, utils::sanitize_html_api_opt, SuccessResponse}; +use lemmy_db_schema::{ + newtypes::DbUrl, + source::{ + comment::{CommentSaved, CommentSavedForm}, + community::{CommunityFollower, CommunityFollowerForm}, + community_block::{CommunityBlock, CommunityBlockForm}, + local_user::{LocalUser, LocalUserUpdateForm}, + person::{Person, PersonUpdateForm}, + person_block::{PersonBlock, PersonBlockForm}, + post::{PostSaved, PostSavedForm}, + }, + traits::{Blockable, Crud, Followable, Saveable}, +}; +use lemmy_db_views::structs::LocalUserView; +use lemmy_utils::{ + error::{LemmyError, LemmyErrorType, LemmyResult}, + spawn_try_task, +}; +use serde::{Deserialize, Serialize}; +use tracing::info; + +/// Maximum number of follow/block URLs which can be imported at once, to prevent server overloading. +/// To import a larger backup, split it into multiple parts. +/// +/// TODO: having the user manually split files will very be confusing +const MAX_URL_IMPORT_COUNT: usize = 1000; + +/// Backup of user data. This struct should never be changed so that the data can be used as a +/// long-term backup in case the instance goes down unexpectedly. All fields are optional to allow +/// importing partial backups. +/// +/// This data should not be parsed by apps/clients, but directly downloaded as a file. +/// +/// Be careful with any changes to this struct, to avoid breaking changes which could prevent +/// importing older backups. +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct UserSettingsBackup { + pub display_name: Option, + pub bio: Option, + pub avatar: Option, + pub banner: Option, + pub matrix_id: Option, + pub bot_account: Option, + // TODO: might be worth making a separate struct for settings backup, to avoid breakage in case + // fields are renamed, and to avoid storing unnecessary fields like person_id or email + pub settings: Option, + #[serde(default)] + pub followed_communities: Vec>, + #[serde(default)] + pub saved_posts: Vec>, + #[serde(default)] + pub saved_comments: Vec>, + #[serde(default)] + pub blocked_communities: Vec>, + #[serde(default)] + pub blocked_users: Vec>, +} + +#[tracing::instrument(skip(context))] +pub async fn export_settings( + local_user_view: LocalUserView, + context: Data, +) -> Result, LemmyError> { + let lists = LocalUser::export_backup(&mut context.pool(), local_user_view.person.id).await?; + + let vec_into = |vec: Vec<_>| vec.into_iter().map(Into::into).collect(); + Ok(Json(UserSettingsBackup { + display_name: local_user_view.person.display_name, + bio: local_user_view.person.bio, + avatar: local_user_view.person.avatar, + banner: local_user_view.person.banner, + matrix_id: local_user_view.person.matrix_user_id, + bot_account: local_user_view.person.bot_account.into(), + settings: Some(local_user_view.local_user), + followed_communities: vec_into(lists.followed_communities), + blocked_communities: vec_into(lists.blocked_communities), + blocked_users: lists.blocked_users.into_iter().map(Into::into).collect(), + saved_posts: lists.saved_posts.into_iter().map(Into::into).collect(), + saved_comments: lists.saved_comments.into_iter().map(Into::into).collect(), + })) +} + +#[tracing::instrument(skip(context))] +pub async fn import_settings( + data: Json, + local_user_view: LocalUserView, + context: Data, +) -> Result, LemmyError> { + let display_name = Some(sanitize_html_api_opt(&data.display_name)); + let bio = Some(sanitize_html_api_opt(&data.bio)); + + let person_form = PersonUpdateForm { + display_name, + bio, + matrix_user_id: Some(data.matrix_id.clone()), + bot_account: data.bot_account, + ..Default::default() + }; + Person::update(&mut context.pool(), local_user_view.person.id, &person_form).await?; + + let local_user_form = LocalUserUpdateForm { + show_nsfw: data.settings.as_ref().map(|s| s.show_nsfw), + theme: data.settings.as_ref().map(|s| s.theme.clone()), + default_sort_type: data.settings.as_ref().map(|s| s.default_sort_type), + default_listing_type: data.settings.as_ref().map(|s| s.default_listing_type), + interface_language: data.settings.as_ref().map(|s| s.interface_language.clone()), + show_avatars: data.settings.as_ref().map(|s| s.show_avatars), + send_notifications_to_email: data + .settings + .as_ref() + .map(|s| s.send_notifications_to_email), + show_scores: data.settings.as_ref().map(|s| s.show_scores), + show_bot_accounts: data.settings.as_ref().map(|s| s.show_bot_accounts), + show_read_posts: data.settings.as_ref().map(|s| s.show_read_posts), + open_links_in_new_tab: data.settings.as_ref().map(|s| s.open_links_in_new_tab), + blur_nsfw: data.settings.as_ref().map(|s| s.blur_nsfw), + auto_expand: data.settings.as_ref().map(|s| s.auto_expand), + infinite_scroll_enabled: data.settings.as_ref().map(|s| s.infinite_scroll_enabled), + post_listing_mode: data.settings.as_ref().map(|s| s.post_listing_mode), + ..Default::default() + }; + LocalUser::update( + &mut context.pool(), + local_user_view.local_user.id, + &local_user_form, + ) + .await?; + + let url_count = data.followed_communities.len() + + data.blocked_communities.len() + + data.blocked_users.len() + + data.saved_posts.len() + + data.saved_comments.len(); + if url_count > MAX_URL_IMPORT_COUNT { + Err(LemmyErrorType::UserBackupTooLarge)?; + } + + spawn_try_task(async move { + const PARALLELISM: usize = 10; + let person_id = local_user_view.person.id; + + // These tasks fetch objects from remote instances which might be down. + // TODO: Would be nice if we could send a list of failed items with api response, but then + // the request would likely timeout. + let mut failed_items = vec![]; + + info!( + "Starting settings backup for {}", + local_user_view.person.name + ); + + futures::stream::iter( + data + .followed_communities + .clone() + .into_iter() + // reset_request_count works like clone, and is necessary to avoid running into request limit + .map(|f| (f, context.reset_request_count())) + .map(|(followed, context)| async move { + // need to reset outgoing request count to avoid running into limit + let community = followed.dereference(&context).await?; + let form = CommunityFollowerForm { + person_id, + community_id: community.id, + pending: true, + }; + CommunityFollower::follow(&mut context.pool(), &form).await?; + LemmyResult::Ok(()) + }), + ) + .buffer_unordered(PARALLELISM) + .collect::>() + .await + .into_iter() + .enumerate() + .for_each(|(i, r)| { + if let Err(e) = r { + failed_items.push(data.followed_communities.get(i).map(|u| u.inner().clone())); + info!("Failed to import followed community: {e}"); + } + }); + + futures::stream::iter( + data + .saved_posts + .clone() + .into_iter() + .map(|s| (s, context.reset_request_count())) + .map(|(saved, context)| async move { + let post = saved.dereference(&context).await?; + let form = PostSavedForm { + person_id, + post_id: post.id, + }; + PostSaved::save(&mut context.pool(), &form).await?; + LemmyResult::Ok(()) + }), + ) + .buffer_unordered(PARALLELISM) + .collect::>() + .await + .into_iter() + .enumerate() + .for_each(|(i, r)| { + if let Err(e) = r { + failed_items.push(data.followed_communities.get(i).map(|u| u.inner().clone())); + info!("Failed to import saved post community: {e}"); + } + }); + + futures::stream::iter( + data + .saved_comments + .clone() + .into_iter() + .map(|s| (s, context.reset_request_count())) + .map(|(saved, context)| async move { + let comment = saved.dereference(&context).await?; + let form = CommentSavedForm { + person_id, + comment_id: comment.id, + }; + CommentSaved::save(&mut context.pool(), &form).await?; + LemmyResult::Ok(()) + }), + ) + .buffer_unordered(PARALLELISM) + .collect::>() + .await + .into_iter() + .enumerate() + .for_each(|(i, r)| { + if let Err(e) = r { + failed_items.push(data.followed_communities.get(i).map(|u| u.inner().clone())); + info!("Failed to import saved comment community: {e}"); + } + }); + + let failed_items: Vec<_> = failed_items.into_iter().flatten().collect(); + info!( + "Finished settings backup for {}, failed items: {:#?}", + local_user_view.person.name, failed_items + ); + + // These tasks don't connect to any remote instances but only insert directly in the database. + // That means the only error condition are db connection failures, so no extra error handling is + // needed. + try_join_all(data.blocked_communities.iter().map(|blocked| async { + // dont fetch unknown blocked objects from home server + let community = blocked.dereference_local(&context).await?; + let form = CommunityBlockForm { + person_id, + community_id: community.id, + }; + CommunityBlock::block(&mut context.pool(), &form).await?; + LemmyResult::Ok(()) + })) + .await?; + + try_join_all(data.blocked_users.iter().map(|blocked| async { + // dont fetch unknown blocked objects from home server + let target = blocked.dereference_local(&context).await?; + let form = PersonBlockForm { + person_id, + target_id: target.id, + }; + PersonBlock::block(&mut context.pool(), &form).await?; + LemmyResult::Ok(()) + })) + .await?; + Ok(()) + }); + + Ok(Json(Default::default())) +} + +#[cfg(test)] +mod tests { + #![allow(clippy::unwrap_used)] + #![allow(clippy::indexing_slicing)] + + use crate::{ + api::user_settings_backup::{export_settings, import_settings}, + objects::tests::init_context, + }; + use activitypub_federation::config::Data; + use lemmy_api_common::context::LemmyContext; + use lemmy_db_schema::{ + source::{ + community::{Community, CommunityFollower, CommunityFollowerForm, CommunityInsertForm}, + instance::Instance, + local_user::{LocalUser, LocalUserInsertForm}, + person::{Person, PersonInsertForm}, + }, + traits::{Crud, Followable}, + }; + use lemmy_db_views::structs::LocalUserView; + use lemmy_db_views_actor::structs::CommunityFollowerView; + use lemmy_utils::error::LemmyErrorType; + use serial_test::serial; + use std::time::Duration; + use tokio::time::sleep; + + async fn create_user( + name: String, + bio: Option, + context: &Data, + ) -> LocalUserView { + let instance = Instance::read_or_create(&mut context.pool(), "example.com".to_string()) + .await + .unwrap(); + let person_form = PersonInsertForm::builder() + .name(name.clone()) + .display_name(Some(name.clone())) + .bio(bio) + .public_key("asd".to_string()) + .instance_id(instance.id) + .build(); + let person = Person::create(&mut context.pool(), &person_form) + .await + .unwrap(); + + let user_form = LocalUserInsertForm::builder() + .person_id(person.id) + .password_encrypted("pass".to_string()) + .build(); + let local_user = LocalUser::create(&mut context.pool(), &user_form) + .await + .unwrap(); + + LocalUserView::read(&mut context.pool(), local_user.id) + .await + .unwrap() + } + + #[tokio::test] + #[serial] + async fn test_settings_export_import() { + let context = init_context().await; + + let export_user = create_user("hanna".to_string(), Some("my bio".to_string()), &context).await; + + let community_form = CommunityInsertForm::builder() + .name("testcom".to_string()) + .title("testcom".to_string()) + .instance_id(export_user.person.instance_id) + .build(); + let community = Community::create(&mut context.pool(), &community_form) + .await + .unwrap(); + let follower_form = CommunityFollowerForm { + community_id: community.id, + person_id: export_user.person.id, + pending: false, + }; + CommunityFollower::follow(&mut context.pool(), &follower_form) + .await + .unwrap(); + + let backup = export_settings(export_user.clone(), context.reset_request_count()) + .await + .unwrap(); + + let import_user = create_user("charles".to_string(), None, &context).await; + + import_settings(backup, import_user.clone(), context.reset_request_count()) + .await + .unwrap(); + let import_user_updated = LocalUserView::read(&mut context.pool(), import_user.local_user.id) + .await + .unwrap(); + + // wait for background task to finish + sleep(Duration::from_millis(100)).await; + + assert_eq!( + export_user.person.display_name, + import_user_updated.person.display_name + ); + assert_eq!(export_user.person.bio, import_user_updated.person.bio); + + let follows = CommunityFollowerView::for_person(&mut context.pool(), import_user.person.id) + .await + .unwrap(); + assert_eq!(follows.len(), 1); + assert_eq!(follows[0].community.actor_id, community.actor_id); + + LocalUser::delete(&mut context.pool(), export_user.local_user.id) + .await + .unwrap(); + LocalUser::delete(&mut context.pool(), import_user.local_user.id) + .await + .unwrap(); + } + + #[tokio::test] + #[serial] + async fn disallow_large_backup() { + let context = init_context().await; + + let export_user = create_user("hanna".to_string(), Some("my bio".to_string()), &context).await; + + let mut backup = export_settings(export_user.clone(), context.reset_request_count()) + .await + .unwrap(); + + for _ in 0..251 { + backup + .followed_communities + .push("http://example.com".parse().unwrap()); + backup + .blocked_communities + .push("http://example2.com".parse().unwrap()); + backup + .saved_posts + .push("http://example3.com".parse().unwrap()); + backup + .saved_comments + .push("http://example4.com".parse().unwrap()); + } + + let import_user = create_user("charles".to_string(), None, &context).await; + + let imported = + import_settings(backup, import_user.clone(), context.reset_request_count()).await; + + assert_eq!( + imported.err().unwrap().error_type, + LemmyErrorType::UserBackupTooLarge + ); + + LocalUser::delete(&mut context.pool(), export_user.local_user.id) + .await + .unwrap(); + LocalUser::delete(&mut context.pool(), import_user.local_user.id) + .await + .unwrap(); + } +} diff --git a/crates/db_schema/src/impls/local_user.rs b/crates/db_schema/src/impls/local_user.rs index 3206322e4..86960c053 100644 --- a/crates/db_schema/src/impls/local_user.rs +++ b/crates/db_schema/src/impls/local_user.rs @@ -1,5 +1,5 @@ use crate::{ - newtypes::LocalUserId, + newtypes::{DbUrl, LocalUserId, PersonId}, schema::local_user::dsl::{ accepted_application, email, @@ -19,7 +19,7 @@ use crate::{ }, }; use bcrypt::{hash, DEFAULT_COST}; -use diesel::{dsl::insert_into, result::Error, ExpressionMethods, QueryDsl}; +use diesel::{dsl::insert_into, result::Error, ExpressionMethods, JoinOnDsl, QueryDsl}; use diesel_async::RunQueryDsl; impl LocalUser { @@ -64,6 +64,78 @@ impl LocalUser { .get_result(conn) .await } + + // TODO: maybe move this and pass in LocalUserView + pub async fn export_backup( + pool: &mut DbPool<'_>, + person_id_: PersonId, + ) -> Result { + use crate::schema::{ + comment, + comment_saved, + community, + community_block, + community_follower, + person, + person_block, + post, + post_saved, + }; + let conn = &mut get_conn(pool).await?; + + let followed_communities = community_follower::dsl::community_follower + .filter(community_follower::person_id.eq(person_id_)) + .inner_join(community::table.on(community_follower::community_id.eq(community::id))) + .select(community::actor_id) + .get_results(conn) + .await?; + + let saved_posts = post_saved::dsl::post_saved + .filter(post_saved::person_id.eq(person_id_)) + .inner_join(post::table.on(post_saved::post_id.eq(post::id))) + .select(post::ap_id) + .get_results(conn) + .await?; + + let saved_comments = comment_saved::dsl::comment_saved + .filter(comment_saved::person_id.eq(person_id_)) + .inner_join(comment::table.on(comment_saved::comment_id.eq(comment::id))) + .select(comment::ap_id) + .get_results(conn) + .await?; + + let blocked_communities = community_block::dsl::community_block + .filter(community_block::person_id.eq(person_id_)) + .inner_join(community::table) + .select(community::actor_id) + .get_results(conn) + .await?; + + let blocked_users = person_block::dsl::person_block + .filter(person_block::person_id.eq(person_id_)) + .inner_join(person::table.on(person_block::target_id.eq(person::id))) + .select(person::actor_id) + .get_results(conn) + .await?; + + // TODO: use join for parallel queries? + + Ok(UserBackupLists { + followed_communities, + saved_posts, + saved_comments, + blocked_communities, + blocked_users, + }) + } +} + +pub struct UserBackupLists { + pub followed_communities: Vec, + pub saved_posts: Vec, + pub saved_comments: Vec, + pub blocked_communities: Vec, + pub blocked_users: Vec, } #[async_trait] diff --git a/crates/db_schema/src/schema.rs b/crates/db_schema/src/schema.rs index 42d90dbec..6942fdccd 100644 --- a/crates/db_schema/src/schema.rs +++ b/crates/db_schema/src/schema.rs @@ -406,6 +406,8 @@ diesel::table! { search_per_second -> Int4, published -> Timestamptz, updated -> Nullable, + import_user_settings -> Int4, + import_user_settings_per_second -> Int4, } } @@ -431,7 +433,6 @@ diesel::table! { show_scores -> Bool, show_bot_accounts -> Bool, show_read_posts -> Bool, - show_new_post_notifs -> Bool, email_verified -> Bool, accepted_application -> Bool, totp_2fa_secret -> Nullable, diff --git a/crates/db_schema/src/source/local_site_rate_limit.rs b/crates/db_schema/src/source/local_site_rate_limit.rs index b16d4e134..af7023f0f 100644 --- a/crates/db_schema/src/source/local_site_rate_limit.rs +++ b/crates/db_schema/src/source/local_site_rate_limit.rs @@ -35,6 +35,8 @@ pub struct LocalSiteRateLimit { pub search_per_second: i32, pub published: DateTime, pub updated: Option>, + pub import_user_settings: i32, + pub import_user_settings_per_second: i32, } #[derive(Clone, TypedBuilder)] @@ -56,6 +58,8 @@ pub struct LocalSiteRateLimitInsertForm { pub comment_per_second: Option, pub search: Option, pub search_per_second: Option, + pub import_user_settings: Option, + pub import_user_settings_per_second: Option, } #[derive(Clone, Default)] @@ -74,5 +78,7 @@ pub struct LocalSiteRateLimitUpdateForm { pub comment_per_second: Option, pub search: Option, pub search_per_second: Option, + pub import_user_settings: Option, + pub import_user_settings_per_second: Option, pub updated: Option>>, } diff --git a/crates/db_schema/src/source/local_user.rs b/crates/db_schema/src/source/local_user.rs index 05c2eaefb..220593698 100644 --- a/crates/db_schema/src/source/local_user.rs +++ b/crates/db_schema/src/source/local_user.rs @@ -40,8 +40,6 @@ pub struct LocalUser { pub show_bot_accounts: bool, /// Whether to show read posts. pub show_read_posts: bool, - /// Whether to show new posts as notifications. - pub show_new_post_notifs: bool, /// Whether their email has been verified. pub email_verified: bool, /// Whether their registration application has been accepted. @@ -82,7 +80,6 @@ pub struct LocalUserInsertForm { pub show_bot_accounts: Option, pub show_scores: Option, pub show_read_posts: Option, - pub show_new_post_notifs: Option, pub email_verified: Option, pub accepted_application: Option, pub totp_2fa_secret: Option>, @@ -112,7 +109,6 @@ pub struct LocalUserUpdateForm { pub show_bot_accounts: Option, pub show_scores: Option, pub show_read_posts: Option, - pub show_new_post_notifs: Option, pub email_verified: Option, pub accepted_application: Option, pub totp_2fa_secret: Option>, diff --git a/crates/db_views/src/registration_application_view.rs b/crates/db_views/src/registration_application_view.rs index b4a952002..c2d49207a 100644 --- a/crates/db_views/src/registration_application_view.rs +++ b/crates/db_views/src/registration_application_view.rs @@ -257,7 +257,6 @@ mod tests { show_bot_accounts: inserted_sara_local_user.show_bot_accounts, show_scores: inserted_sara_local_user.show_scores, show_read_posts: inserted_sara_local_user.show_read_posts, - show_new_post_notifs: inserted_sara_local_user.show_new_post_notifs, email_verified: inserted_sara_local_user.email_verified, accepted_application: inserted_sara_local_user.accepted_application, totp_2fa_secret: inserted_sara_local_user.totp_2fa_secret, diff --git a/crates/utils/src/error.rs b/crates/utils/src/error.rs index f5b7a0be8..b6a4fe4ec 100644 --- a/crates/utils/src/error.rs +++ b/crates/utils/src/error.rs @@ -215,6 +215,7 @@ pub enum LemmyErrorType { InstanceBlockAlreadyExists, /// `jwt` cookie must be marked secure and httponly AuthCookieInsecure, + UserBackupTooLarge, Unknown(String), } diff --git a/crates/utils/src/rate_limit/mod.rs b/crates/utils/src/rate_limit/mod.rs index 1bb6f1b5f..114daf452 100644 --- a/crates/utils/src/rate_limit/mod.rs +++ b/crates/utils/src/rate_limit/mod.rs @@ -57,6 +57,12 @@ pub struct RateLimitConfig { #[builder(default = 600)] /// Interval length for search limit, in seconds pub search_per_second: i32, + #[builder(default = 1)] + /// Maximum number of user settings imports in interval + pub import_user_settings: i32, + #[builder(default = 24 * 60 * 60)] + /// Interval length for importing user settings, in seconds (defaults to 24 hours) + pub import_user_settings_per_second: i32, } #[derive(Debug, Clone)] @@ -125,6 +131,7 @@ impl RateLimitCell { RateLimitType::Image => rate_limit.image_per_second, RateLimitType::Comment => rate_limit.comment_per_second, RateLimitType::Search => rate_limit.search_per_second, + RateLimitType::ImportUserSettings => rate_limit.import_user_settings_per_second } .into_values() .max() @@ -162,6 +169,10 @@ impl RateLimitCell { self.kind(RateLimitType::Search) } + pub fn import_user_settings(&self) -> RateLimitedGuard { + self.kind(RateLimitType::ImportUserSettings) + } + fn kind(&self, type_: RateLimitType) -> RateLimitedGuard { RateLimitedGuard { rate_limit: self.rate_limit.clone(), @@ -193,6 +204,10 @@ impl RateLimitedGuard { RateLimitType::Image => (rate_limit.image, rate_limit.image_per_second), RateLimitType::Comment => (rate_limit.comment, rate_limit.comment_per_second), RateLimitType::Search => (rate_limit.search, rate_limit.search_per_second), + RateLimitType::ImportUserSettings => ( + rate_limit.import_user_settings, + rate_limit.import_user_settings_per_second, + ), }; let limiter = &mut guard.rate_limiter; diff --git a/crates/utils/src/rate_limit/rate_limiter.rs b/crates/utils/src/rate_limit/rate_limiter.rs index 3acf23ba4..7ba1345c5 100644 --- a/crates/utils/src/rate_limit/rate_limiter.rs +++ b/crates/utils/src/rate_limit/rate_limiter.rs @@ -53,6 +53,7 @@ pub(crate) enum RateLimitType { Image, Comment, Search, + ImportUserSettings, } type Map = HashMap>; diff --git a/migrations/2023-09-20-110614_drop-show-new-post-notifs/down.sql b/migrations/2023-09-20-110614_drop-show-new-post-notifs/down.sql new file mode 100644 index 000000000..ac8e06833 --- /dev/null +++ b/migrations/2023-09-20-110614_drop-show-new-post-notifs/down.sql @@ -0,0 +1,3 @@ +ALTER TABLE local_user + ADD COLUMN show_new_post_notifs boolean NOT NULL DEFAULT FALSE; + diff --git a/migrations/2023-09-20-110614_drop-show-new-post-notifs/up.sql b/migrations/2023-09-20-110614_drop-show-new-post-notifs/up.sql new file mode 100644 index 000000000..513b06634 --- /dev/null +++ b/migrations/2023-09-20-110614_drop-show-new-post-notifs/up.sql @@ -0,0 +1,4 @@ +-- this setting is unused with websocket gone +ALTER TABLE local_user + DROP COLUMN show_new_post_notifs; + diff --git a/migrations/2023-09-28-084231_import_user_settings_rate_limit/down.sql b/migrations/2023-09-28-084231_import_user_settings_rate_limit/down.sql new file mode 100644 index 000000000..edbb7dd18 --- /dev/null +++ b/migrations/2023-09-28-084231_import_user_settings_rate_limit/down.sql @@ -0,0 +1,6 @@ +ALTER TABLE local_site_rate_limit + DROP COLUMN import_user_settings; + +ALTER TABLE local_site_rate_limit + DROP COLUMN import_user_settings_per_second; + diff --git a/migrations/2023-09-28-084231_import_user_settings_rate_limit/up.sql b/migrations/2023-09-28-084231_import_user_settings_rate_limit/up.sql new file mode 100644 index 000000000..6ff73f94b --- /dev/null +++ b/migrations/2023-09-28-084231_import_user_settings_rate_limit/up.sql @@ -0,0 +1,6 @@ +ALTER TABLE local_site_rate_limit + ADD COLUMN import_user_settings int NOT NULL DEFAULT 1; + +ALTER TABLE local_site_rate_limit + ADD COLUMN import_user_settings_per_second int NOT NULL DEFAULT 86400; + diff --git a/src/api_routes_http.rs b/src/api_routes_http.rs index 173bce199..3546b3400 100644 --- a/src/api_routes_http.rs +++ b/src/api_routes_http.rs @@ -121,6 +121,7 @@ use lemmy_apub::api::{ read_person::read_person, resolve_object::resolve_object, search::search, + user_settings_backup::{export_settings, import_settings}, }; use lemmy_utils::rate_limit::RateLimitCell; @@ -297,6 +298,12 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimitCell) { .route("/totp/update", web::post().to(update_totp)) .route("/list_logins", web::get().to(list_logins)), ) + .service( + web::scope("/user") + .wrap(rate_limit.import_user_settings()) + .route("/export_settings", web::get().to(export_settings)) + .route("/import_settings", web::post().to(import_settings)), + ) // Admin Actions .service( web::scope("/admin") From 291ff19718334645376279f58ad64314986cc200 Mon Sep 17 00:00:00 2001 From: Nutomic Date: Wed, 11 Oct 2023 16:48:19 +0200 Subject: [PATCH 02/24] Only sanitize strings when generating RSS feeds and emails (fixes #4003) (#4024) * Only sanitize strings when generating RSS feeds and emails (fixes #4003) * clippy * fix test --- api_tests/src/post.spec.ts | 26 ---------------- crates/api/src/comment_report/create.rs | 4 +-- crates/api/src/community/ban.rs | 4 +-- crates/api/src/community/hide.rs | 4 +-- crates/api/src/local_user/ban_person.rs | 4 +-- crates/api/src/local_user/save_settings.rs | 12 +++---- crates/api/src/post_report/create.rs | 4 +-- .../api/src/private_message_report/create.rs | 6 ++-- crates/api/src/site/purge/comment.rs | 5 ++- crates/api/src/site/purge/community.rs | 5 ++- crates/api/src/site/purge/person.rs | 5 ++- crates/api/src/site/purge/post.rs | 5 ++- crates/api_common/src/build_response.rs | 14 ++++++--- crates/api_common/src/utils.rs | 31 ------------------- crates/api_crud/src/comment/create.rs | 2 -- crates/api_crud/src/comment/update.rs | 3 +- crates/api_crud/src/community/create.rs | 18 ++++------- crates/api_crud/src/community/update.rs | 9 ++---- crates/api_crud/src/custom_emoji/create.rs | 12 +++---- crates/api_crud/src/custom_emoji/update.rs | 9 ++---- crates/api_crud/src/post/create.rs | 11 ++----- crates/api_crud/src/post/update.rs | 12 ++----- crates/api_crud/src/private_message/create.rs | 7 ++--- crates/api_crud/src/private_message/update.rs | 5 ++- crates/api_crud/src/site/create.rs | 27 +++++----------- crates/api_crud/src/site/update.rs | 22 +++++-------- crates/api_crud/src/user/create.rs | 10 +----- .../apub/src/activities/block/block_user.rs | 6 ++-- .../src/activities/block/undo_block_user.rs | 6 ++-- .../apub/src/activities/community/report.rs | 6 ++-- crates/apub/src/activities/deletion/delete.rs | 4 +-- crates/apub/src/objects/comment.rs | 6 +--- crates/apub/src/objects/instance.rs | 9 ++---- crates/apub/src/objects/person.rs | 16 +++------- crates/apub/src/objects/post.rs | 18 ++--------- crates/apub/src/objects/private_message.rs | 6 +--- crates/apub/src/protocol/objects/group.rs | 12 ++----- crates/routes/src/feeds.rs | 18 ++++++----- crates/utils/src/utils/markdown.rs | 28 ++++++++++++++++- crates/utils/translations | 2 +- 40 files changed, 140 insertions(+), 273 deletions(-) diff --git a/api_tests/src/post.spec.ts b/api_tests/src/post.spec.ts index 49840d49f..30a176fee 100644 --- a/api_tests/src/post.spec.ts +++ b/api_tests/src/post.spec.ts @@ -554,29 +554,3 @@ test("Report a post", async () => { expect(betaReport.original_post_body).toBe(alphaReport.original_post_body); expect(betaReport.reason).toBe(alphaReport.reason); }); - -test("Sanitize HTML", async () => { - let betaCommunity = (await resolveBetaCommunity(beta)).community; - if (!betaCommunity) { - throw "Missing beta community"; - } - - let name = randomString(5); - let body = " hello &\"'"; - let form: CreatePost = { - name, - body, - community_id: betaCommunity.community.id, - }; - let post = await beta.createPost(form); - // first escaping for the api - expect(post.post_view.post.body).toBe( - "<script>alert('xss');</script> hello &"'", - ); - - let alphaPost = (await resolvePost(alpha, post.post_view.post)).post; - // second escaping over federation, avoid double escape of & - expect(alphaPost?.post.body).toBe( - "<script>alert('xss');</script> hello &"'", - ); -}); diff --git a/crates/api/src/comment_report/create.rs b/crates/api/src/comment_report/create.rs index 3c1304674..e1040fd5e 100644 --- a/crates/api/src/comment_report/create.rs +++ b/crates/api/src/comment_report/create.rs @@ -5,7 +5,7 @@ use lemmy_api_common::{ comment::{CommentReportResponse, CreateCommentReport}, context::LemmyContext, send_activity::{ActivityChannel, SendActivityData}, - utils::{check_community_ban, sanitize_html_api, send_new_report_email_to_admins}, + utils::{check_community_ban, send_new_report_email_to_admins}, }; use lemmy_db_schema::{ source::{ @@ -26,7 +26,7 @@ pub async fn create_comment_report( ) -> Result, LemmyError> { let local_site = LocalSite::read(&mut context.pool()).await?; - let reason = sanitize_html_api(data.reason.trim()); + let reason = data.reason.trim().to_string(); check_report_reason(&reason, &local_site)?; let person_id = local_user_view.person.id; diff --git a/crates/api/src/community/ban.rs b/crates/api/src/community/ban.rs index c2f70b7c0..2cc40cd1d 100644 --- a/crates/api/src/community/ban.rs +++ b/crates/api/src/community/ban.rs @@ -4,7 +4,7 @@ use lemmy_api_common::{ community::{BanFromCommunity, BanFromCommunityResponse}, context::LemmyContext, send_activity::{ActivityChannel, SendActivityData}, - utils::{is_mod_or_admin, remove_user_data_in_community, sanitize_html_api_opt}, + utils::{is_mod_or_admin, remove_user_data_in_community}, }; use lemmy_db_schema::{ source::{ @@ -81,7 +81,7 @@ pub async fn ban_from_community( mod_person_id: local_user_view.person.id, other_person_id: data.person_id, community_id: data.community_id, - reason: sanitize_html_api_opt(&data.reason), + reason: data.reason.clone(), banned: Some(data.ban), expires, }; diff --git a/crates/api/src/community/hide.rs b/crates/api/src/community/hide.rs index 5f2b9d6fb..dd8343927 100644 --- a/crates/api/src/community/hide.rs +++ b/crates/api/src/community/hide.rs @@ -5,7 +5,7 @@ use lemmy_api_common::{ community::{CommunityResponse, HideCommunity}, context::LemmyContext, send_activity::{ActivityChannel, SendActivityData}, - utils::{is_admin, sanitize_html_api_opt}, + utils::is_admin, }; use lemmy_db_schema::{ source::{ @@ -34,7 +34,7 @@ pub async fn hide_community( let mod_hide_community_form = ModHideCommunityForm { community_id: data.community_id, mod_person_id: local_user_view.person.id, - reason: sanitize_html_api_opt(&data.reason), + reason: data.reason.clone(), hidden: Some(data.hidden), }; diff --git a/crates/api/src/local_user/ban_person.rs b/crates/api/src/local_user/ban_person.rs index e8eaecc0a..8ff203f0e 100644 --- a/crates/api/src/local_user/ban_person.rs +++ b/crates/api/src/local_user/ban_person.rs @@ -4,7 +4,7 @@ use lemmy_api_common::{ context::LemmyContext, person::{BanPerson, BanPersonResponse}, send_activity::{ActivityChannel, SendActivityData}, - utils::{is_admin, remove_user_data, sanitize_html_api_opt}, + utils::{is_admin, remove_user_data}, }; use lemmy_db_schema::{ source::{ @@ -61,7 +61,7 @@ pub async fn ban_from_site( let form = ModBanForm { mod_person_id: local_user_view.person.id, other_person_id: data.person_id, - reason: sanitize_html_api_opt(&data.reason), + reason: data.reason.clone(), banned: Some(data.ban), expires, }; diff --git a/crates/api/src/local_user/save_settings.rs b/crates/api/src/local_user/save_settings.rs index c3d7eca23..d219416f8 100644 --- a/crates/api/src/local_user/save_settings.rs +++ b/crates/api/src/local_user/save_settings.rs @@ -2,7 +2,7 @@ use actix_web::web::{Data, Json}; use lemmy_api_common::{ context::LemmyContext, person::SaveUserSettings, - utils::{sanitize_html_api_opt, send_verification_email}, + utils::send_verification_email, SuccessResponse, }; use lemmy_db_schema::{ @@ -28,13 +28,10 @@ pub async fn save_user_settings( ) -> Result, LemmyError> { let site_view = SiteView::read_local(&mut context.pool()).await?; - let bio = sanitize_html_api_opt(&data.bio); - let display_name = sanitize_html_api_opt(&data.display_name); - let avatar = diesel_option_overwrite_to_url(&data.avatar)?; let banner = diesel_option_overwrite_to_url(&data.banner)?; - let bio = diesel_option_overwrite(bio); - let display_name = diesel_option_overwrite(display_name); + let bio = diesel_option_overwrite(data.bio.clone()); + let display_name = diesel_option_overwrite(data.display_name.clone()); let matrix_user_id = diesel_option_overwrite(data.matrix_user_id.clone()); let email_deref = data.email.as_deref().map(str::to_lowercase); let email = diesel_option_overwrite(email_deref.clone()); @@ -82,7 +79,6 @@ pub async fn save_user_settings( let person_id = local_user_view.person.id; let default_listing_type = data.default_listing_type; let default_sort_type = data.default_sort_type; - let theme = sanitize_html_api_opt(&data.theme); let person_form = PersonUpdateForm { display_name, @@ -114,7 +110,7 @@ pub async fn save_user_settings( show_scores: data.show_scores, default_sort_type, default_listing_type, - theme, + theme: data.theme.clone(), interface_language: data.interface_language.clone(), open_links_in_new_tab: data.open_links_in_new_tab, infinite_scroll_enabled: data.infinite_scroll_enabled, diff --git a/crates/api/src/post_report/create.rs b/crates/api/src/post_report/create.rs index 6a32d505b..126475c0c 100644 --- a/crates/api/src/post_report/create.rs +++ b/crates/api/src/post_report/create.rs @@ -5,7 +5,7 @@ use lemmy_api_common::{ context::LemmyContext, post::{CreatePostReport, PostReportResponse}, send_activity::{ActivityChannel, SendActivityData}, - utils::{check_community_ban, sanitize_html_api, send_new_report_email_to_admins}, + utils::{check_community_ban, send_new_report_email_to_admins}, }; use lemmy_db_schema::{ source::{ @@ -26,7 +26,7 @@ pub async fn create_post_report( ) -> Result, LemmyError> { let local_site = LocalSite::read(&mut context.pool()).await?; - let reason = sanitize_html_api(data.reason.trim()); + let reason = data.reason.trim().to_string(); check_report_reason(&reason, &local_site)?; let person_id = local_user_view.person.id; diff --git a/crates/api/src/private_message_report/create.rs b/crates/api/src/private_message_report/create.rs index 2c4291581..75620bf8b 100644 --- a/crates/api/src/private_message_report/create.rs +++ b/crates/api/src/private_message_report/create.rs @@ -3,7 +3,7 @@ use actix_web::web::{Data, Json}; use lemmy_api_common::{ context::LemmyContext, private_message::{CreatePrivateMessageReport, PrivateMessageReportResponse}, - utils::{sanitize_html_api, send_new_report_email_to_admins}, + utils::send_new_report_email_to_admins, }; use lemmy_db_schema::{ source::{ @@ -24,7 +24,7 @@ pub async fn create_pm_report( ) -> Result, LemmyError> { let local_site = LocalSite::read(&mut context.pool()).await?; - let reason = sanitize_html_api(data.reason.trim()); + let reason = data.reason.trim().to_string(); check_report_reason(&reason, &local_site)?; let person_id = local_user_view.person.id; @@ -35,7 +35,7 @@ pub async fn create_pm_report( creator_id: person_id, private_message_id, original_pm_text: private_message.content, - reason: reason.clone(), + reason, }; let report = PrivateMessageReport::report(&mut context.pool(), &report_form) diff --git a/crates/api/src/site/purge/comment.rs b/crates/api/src/site/purge/comment.rs index ec4ef02af..1537bc416 100644 --- a/crates/api/src/site/purge/comment.rs +++ b/crates/api/src/site/purge/comment.rs @@ -2,7 +2,7 @@ use actix_web::web::{Data, Json}; use lemmy_api_common::{ context::LemmyContext, site::{PurgeComment, PurgeItemResponse}, - utils::{is_admin, sanitize_html_api_opt}, + utils::is_admin, }; use lemmy_db_schema::{ source::{ @@ -35,10 +35,9 @@ pub async fn purge_comment( Comment::delete(&mut context.pool(), comment_id).await?; // Mod tables - let reason = sanitize_html_api_opt(&data.reason); let form = AdminPurgeCommentForm { admin_person_id: local_user_view.person.id, - reason, + reason: data.reason.clone(), post_id, }; diff --git a/crates/api/src/site/purge/community.rs b/crates/api/src/site/purge/community.rs index 8e731fa29..6ca30125d 100644 --- a/crates/api/src/site/purge/community.rs +++ b/crates/api/src/site/purge/community.rs @@ -3,7 +3,7 @@ use lemmy_api_common::{ context::LemmyContext, request::purge_image_from_pictrs, site::{PurgeCommunity, PurgeItemResponse}, - utils::{is_admin, purge_image_posts_for_community, sanitize_html_api_opt}, + utils::{is_admin, purge_image_posts_for_community}, }; use lemmy_db_schema::{ source::{ @@ -42,10 +42,9 @@ pub async fn purge_community( Community::delete(&mut context.pool(), community_id).await?; // Mod tables - let reason = sanitize_html_api_opt(&data.reason); let form = AdminPurgeCommunityForm { admin_person_id: local_user_view.person.id, - reason, + reason: data.reason.clone(), }; AdminPurgeCommunity::create(&mut context.pool(), &form).await?; diff --git a/crates/api/src/site/purge/person.rs b/crates/api/src/site/purge/person.rs index 1e68db88f..96b8f7859 100644 --- a/crates/api/src/site/purge/person.rs +++ b/crates/api/src/site/purge/person.rs @@ -3,7 +3,7 @@ use lemmy_api_common::{ context::LemmyContext, request::delete_image_from_pictrs, site::{PurgeItemResponse, PurgePerson}, - utils::{is_admin, sanitize_html_api_opt}, + utils::is_admin, }; use lemmy_db_schema::{ source::{ @@ -41,10 +41,9 @@ pub async fn purge_person( Person::delete(&mut context.pool(), person_id).await?; // Mod tables - let reason = sanitize_html_api_opt(&data.reason); let form = AdminPurgePersonForm { admin_person_id: local_user_view.person.id, - reason, + reason: data.reason.clone(), }; AdminPurgePerson::create(&mut context.pool(), &form).await?; diff --git a/crates/api/src/site/purge/post.rs b/crates/api/src/site/purge/post.rs index 5e1ff4ea5..a7469aa90 100644 --- a/crates/api/src/site/purge/post.rs +++ b/crates/api/src/site/purge/post.rs @@ -3,7 +3,7 @@ use lemmy_api_common::{ context::LemmyContext, request::purge_image_from_pictrs, site::{PurgeItemResponse, PurgePost}, - utils::{is_admin, sanitize_html_api_opt}, + utils::is_admin, }; use lemmy_db_schema::{ source::{ @@ -43,10 +43,9 @@ pub async fn purge_post( Post::delete(&mut context.pool(), post_id).await?; // Mod tables - let reason = sanitize_html_api_opt(&data.reason); let form = AdminPurgePostForm { admin_person_id: local_user_view.person.id, - reason, + reason: data.reason.clone(), community_id, }; diff --git a/crates/api_common/src/build_response.rs b/crates/api_common/src/build_response.rs index e0d399764..5083616c1 100644 --- a/crates/api_common/src/build_response.rs +++ b/crates/api_common/src/build_response.rs @@ -20,7 +20,10 @@ use lemmy_db_schema::{ }; use lemmy_db_views::structs::{CommentView, LocalUserView, PostView}; use lemmy_db_views_actor::structs::CommunityView; -use lemmy_utils::{error::LemmyError, utils::mention::MentionData}; +use lemmy_utils::{ + error::LemmyError, + utils::{markdown::markdown_to_html, mention::MentionData}, +}; pub async fn build_comment_response( context: &LemmyContext, @@ -121,10 +124,11 @@ pub async fn send_local_notifs( // Send an email to those local users that have notifications on if do_send_email { let lang = get_interface_language(&mention_user_view); + let content = markdown_to_html(&comment.content); send_email_to_user( &mention_user_view, &lang.notification_mentioned_by_subject(&person.name), - &lang.notification_mentioned_by_body(&comment.content, &inbox_link, &person.name), + &lang.notification_mentioned_by_body(&content, &inbox_link, &person.name), context.settings(), ) .await @@ -164,10 +168,11 @@ pub async fn send_local_notifs( if do_send_email { let lang = get_interface_language(&parent_user_view); + let content = markdown_to_html(&comment.content); send_email_to_user( &parent_user_view, &lang.notification_comment_reply_subject(&person.name), - &lang.notification_comment_reply_body(&comment.content, &inbox_link, &person.name), + &lang.notification_comment_reply_body(&content, &inbox_link, &person.name), context.settings(), ) .await @@ -201,10 +206,11 @@ pub async fn send_local_notifs( if do_send_email { let lang = get_interface_language(&parent_user_view); + let content = markdown_to_html(&comment.content); send_email_to_user( &parent_user_view, &lang.notification_post_reply_subject(&person.name), - &lang.notification_post_reply_body(&comment.content, &inbox_link, &person.name), + &lang.notification_post_reply_body(&content, &inbox_link, &person.name), context.settings(), ) .await diff --git a/crates/api_common/src/utils.rs b/crates/api_common/src/utils.rs index 891a35855..4dfad3645 100644 --- a/crates/api_common/src/utils.rs +++ b/crates/api_common/src/utils.rs @@ -723,37 +723,6 @@ pub fn generate_moderators_url(community_id: &DbUrl) -> Result` is left in place because it is interpreted as markdown quote. -pub fn sanitize_html_api(data: &str) -> String { - data - .replace('&', "&") - .replace('<', "<") - .replace('\"', """) - .replace('\'', "'") -} - -pub fn sanitize_html_api_opt(data: &Option) -> Option { - data.as_ref().map(|d| sanitize_html_api(d)) -} - -/// Replace special HTML characters in federation parameters to prevent XSS attacks. -/// -/// Unlike [sanitize_html_api()] it leaves `&` in place to avoid double escaping. -pub fn sanitize_html_federation(data: &str) -> String { - data - .replace('<', "<") - .replace('\"', """) - .replace('\'', "'") -} - -pub fn sanitize_html_federation_opt(data: &Option) -> Option { - data.as_ref().map(|d| sanitize_html_federation(d)) -} - pub fn create_login_cookie(jwt: Sensitive) -> Cookie<'static> { let mut cookie = Cookie::new(AUTH_COOKIE_NAME, jwt.into_inner()); cookie.set_secure(true); diff --git a/crates/api_crud/src/comment/create.rs b/crates/api_crud/src/comment/create.rs index c9aa4f774..dcf998bd7 100644 --- a/crates/api_crud/src/comment/create.rs +++ b/crates/api_crud/src/comment/create.rs @@ -12,7 +12,6 @@ use lemmy_api_common::{ generate_local_apub_endpoint, get_post, local_site_to_slur_regex, - sanitize_html_api, EndpointType, }, }; @@ -52,7 +51,6 @@ pub async fn create_comment( &local_site_to_slur_regex(&local_site), ); is_valid_body_field(&Some(content.clone()), false)?; - let content = sanitize_html_api(&content); // Check for a community ban let post_id = data.post_id; diff --git a/crates/api_crud/src/comment/update.rs b/crates/api_crud/src/comment/update.rs index 2d966d0fa..d6c672262 100644 --- a/crates/api_crud/src/comment/update.rs +++ b/crates/api_crud/src/comment/update.rs @@ -5,7 +5,7 @@ use lemmy_api_common::{ comment::{CommentResponse, EditComment}, context::LemmyContext, send_activity::{ActivityChannel, SendActivityData}, - utils::{check_community_ban, local_site_to_slur_regex, sanitize_html_api_opt}, + utils::{check_community_ban, local_site_to_slur_regex}, }; use lemmy_db_schema::{ source::{ @@ -63,7 +63,6 @@ pub async fn update_comment( .as_ref() .map(|c| remove_slurs(c, &local_site_to_slur_regex(&local_site))); is_valid_body_field(&content, false)?; - let content = sanitize_html_api_opt(&content); let comment_id = data.comment_id; let form = CommentUpdateForm { diff --git a/crates/api_crud/src/community/create.rs b/crates/api_crud/src/community/create.rs index 633fa81a9..91725c409 100644 --- a/crates/api_crud/src/community/create.rs +++ b/crates/api_crud/src/community/create.rs @@ -11,8 +11,6 @@ use lemmy_api_common::{ generate_shared_inbox_url, is_admin, local_site_to_slur_regex, - sanitize_html_api, - sanitize_html_api_opt, EndpointType, }, }; @@ -57,14 +55,10 @@ pub async fn create_community( let icon = diesel_option_overwrite_to_url_create(&data.icon)?; let banner = diesel_option_overwrite_to_url_create(&data.banner)?; - let name = sanitize_html_api(&data.name); - let title = sanitize_html_api(&data.title); - let description = sanitize_html_api_opt(&data.description); - let slur_regex = local_site_to_slur_regex(&local_site); - check_slurs(&name, &slur_regex)?; - check_slurs(&title, &slur_regex)?; - check_slurs_opt(&description, &slur_regex)?; + check_slurs(&data.name, &slur_regex)?; + check_slurs(&data.title, &slur_regex)?; + check_slurs_opt(&data.description, &slur_regex)?; is_valid_actor_name(&data.name, local_site.actor_name_max_length as usize)?; is_valid_body_field(&data.description, false)?; @@ -85,9 +79,9 @@ pub async fn create_community( let keypair = generate_actor_keypair()?; let community_form = CommunityInsertForm::builder() - .name(name) - .title(title) - .description(description) + .name(data.name.clone()) + .title(data.title.clone()) + .description(data.description.clone()) .icon(icon) .banner(banner) .nsfw(data.nsfw) diff --git a/crates/api_crud/src/community/update.rs b/crates/api_crud/src/community/update.rs index 893128d8c..8e5ec77c0 100644 --- a/crates/api_crud/src/community/update.rs +++ b/crates/api_crud/src/community/update.rs @@ -5,7 +5,7 @@ use lemmy_api_common::{ community::{CommunityResponse, EditCommunity}, context::LemmyContext, send_activity::{ActivityChannel, SendActivityData}, - utils::{local_site_to_slur_regex, sanitize_html_api_opt}, + utils::local_site_to_slur_regex, }; use lemmy_db_schema::{ newtypes::PersonId, @@ -37,12 +37,9 @@ pub async fn update_community( check_slurs_opt(&data.description, &slur_regex)?; is_valid_body_field(&data.description, false)?; - let title = sanitize_html_api_opt(&data.title); - let description = sanitize_html_api_opt(&data.description); - let icon = diesel_option_overwrite_to_url(&data.icon)?; let banner = diesel_option_overwrite_to_url(&data.banner)?; - let description = diesel_option_overwrite(description); + let description = diesel_option_overwrite(data.description.clone()); // Verify its a mod (only mods can edit it) let community_id = data.community_id; @@ -67,7 +64,7 @@ pub async fn update_community( } let community_form = CommunityUpdateForm { - title, + title: data.title.clone(), description, icon, banner, diff --git a/crates/api_crud/src/custom_emoji/create.rs b/crates/api_crud/src/custom_emoji/create.rs index 7a2ae9b2d..cd30ef1e9 100644 --- a/crates/api_crud/src/custom_emoji/create.rs +++ b/crates/api_crud/src/custom_emoji/create.rs @@ -3,7 +3,7 @@ use actix_web::web::Json; use lemmy_api_common::{ context::LemmyContext, custom_emoji::{CreateCustomEmoji, CustomEmojiResponse}, - utils::{is_admin, sanitize_html_api}, + utils::is_admin, }; use lemmy_db_schema::source::{ custom_emoji::{CustomEmoji, CustomEmojiInsertForm}, @@ -23,15 +23,11 @@ pub async fn create_custom_emoji( // Make sure user is an admin is_admin(&local_user_view)?; - let shortcode = sanitize_html_api(data.shortcode.to_lowercase().trim()); - let alt_text = sanitize_html_api(&data.alt_text); - let category = sanitize_html_api(&data.category); - let emoji_form = CustomEmojiInsertForm::builder() .local_site_id(local_site.id) - .shortcode(shortcode) - .alt_text(alt_text) - .category(category) + .shortcode(data.shortcode.to_lowercase().trim().to_string()) + .alt_text(data.alt_text.to_string()) + .category(data.category.to_string()) .image_url(data.clone().image_url.into()) .build(); let emoji = CustomEmoji::create(&mut context.pool(), &emoji_form).await?; diff --git a/crates/api_crud/src/custom_emoji/update.rs b/crates/api_crud/src/custom_emoji/update.rs index 5c115d5c1..5a2631a62 100644 --- a/crates/api_crud/src/custom_emoji/update.rs +++ b/crates/api_crud/src/custom_emoji/update.rs @@ -3,7 +3,7 @@ use actix_web::web::Json; use lemmy_api_common::{ context::LemmyContext, custom_emoji::{CustomEmojiResponse, EditCustomEmoji}, - utils::{is_admin, sanitize_html_api}, + utils::is_admin, }; use lemmy_db_schema::source::{ custom_emoji::{CustomEmoji, CustomEmojiUpdateForm}, @@ -23,13 +23,10 @@ pub async fn update_custom_emoji( // Make sure user is an admin is_admin(&local_user_view)?; - let alt_text = sanitize_html_api(&data.alt_text); - let category = sanitize_html_api(&data.category); - let emoji_form = CustomEmojiUpdateForm::builder() .local_site_id(local_site.id) - .alt_text(alt_text) - .category(category) + .alt_text(data.alt_text.to_string()) + .category(data.category.to_string()) .image_url(data.clone().image_url.into()) .build(); let emoji = CustomEmoji::update(&mut context.pool(), data.id, &emoji_form).await?; diff --git a/crates/api_crud/src/post/create.rs b/crates/api_crud/src/post/create.rs index 4fb97c1c7..33408b774 100644 --- a/crates/api_crud/src/post/create.rs +++ b/crates/api_crud/src/post/create.rs @@ -13,8 +13,6 @@ use lemmy_api_common::{ honeypot_check, local_site_to_slur_regex, mark_post_as_read, - sanitize_html_api, - sanitize_html_api_opt, EndpointType, }, }; @@ -92,11 +90,6 @@ pub async fn create_post( .map(|u| (u.title, u.description, u.embed_video_url)) .unwrap_or_default(); - let name = sanitize_html_api(data.name.trim()); - let body = sanitize_html_api_opt(&data.body); - let embed_title = sanitize_html_api_opt(&embed_title); - let embed_description = sanitize_html_api_opt(&embed_description); - // Only need to check if language is allowed in case user set it explicitly. When using default // language, it already only returns allowed languages. CommunityLanguage::is_allowed_community_language( @@ -120,9 +113,9 @@ pub async fn create_post( }; let post_form = PostInsertForm::builder() - .name(name) + .name(data.name.trim().to_string()) .url(url) - .body(body) + .body(data.body.clone()) .community_id(data.community_id) .creator_id(local_user_view.person.id) .nsfw(data.nsfw) diff --git a/crates/api_crud/src/post/update.rs b/crates/api_crud/src/post/update.rs index 04e6191a8..dfd311f3f 100644 --- a/crates/api_crud/src/post/update.rs +++ b/crates/api_crud/src/post/update.rs @@ -6,7 +6,7 @@ use lemmy_api_common::{ post::{EditPost, PostResponse}, request::fetch_site_data, send_activity::{ActivityChannel, SendActivityData}, - utils::{check_community_ban, local_site_to_slur_regex, sanitize_html_api_opt}, + utils::{check_community_ban, local_site_to_slur_regex}, }; use lemmy_db_schema::{ source::{ @@ -75,12 +75,6 @@ pub async fn update_post( .map(|u| (Some(u.title), Some(u.description), Some(u.embed_video_url))) .unwrap_or_default(); - let name = sanitize_html_api_opt(&data.name); - let body = sanitize_html_api_opt(&data.body); - let body = diesel_option_overwrite(body); - let embed_title = embed_title.map(|e| sanitize_html_api_opt(&e)); - let embed_description = embed_description.map(|e| sanitize_html_api_opt(&e)); - let language_id = data.language_id; CommunityLanguage::is_allowed_community_language( &mut context.pool(), @@ -90,9 +84,9 @@ pub async fn update_post( .await?; let post_form = PostUpdateForm { - name, + name: data.name.clone(), url, - body, + body: diesel_option_overwrite(data.body.clone()), nsfw: data.nsfw, embed_title, embed_description, diff --git a/crates/api_crud/src/private_message/create.rs b/crates/api_crud/src/private_message/create.rs index c0bdb8ed1..a176cdcb2 100644 --- a/crates/api_crud/src/private_message/create.rs +++ b/crates/api_crud/src/private_message/create.rs @@ -9,7 +9,6 @@ use lemmy_api_common::{ generate_local_apub_endpoint, get_interface_language, local_site_to_slur_regex, - sanitize_html_api, send_email_to_user, EndpointType, }, @@ -24,7 +23,7 @@ use lemmy_db_schema::{ use lemmy_db_views::structs::{LocalUserView, PrivateMessageView}; use lemmy_utils::{ error::{LemmyError, LemmyErrorExt, LemmyErrorType}, - utils::{slurs::remove_slurs, validation::is_valid_body_field}, + utils::{markdown::markdown_to_html, slurs::remove_slurs, validation::is_valid_body_field}, }; #[tracing::instrument(skip(context))] @@ -35,8 +34,7 @@ pub async fn create_private_message( ) -> Result, LemmyError> { let local_site = LocalSite::read(&mut context.pool()).await?; - let content = sanitize_html_api(&data.content); - let content = remove_slurs(&content, &local_site_to_slur_regex(&local_site)); + let content = remove_slurs(&data.content, &local_site_to_slur_regex(&local_site)); is_valid_body_field(&Some(content.clone()), false)?; check_person_block( @@ -83,6 +81,7 @@ pub async fn create_private_message( let lang = get_interface_language(&local_recipient); let inbox_link = format!("{}/inbox", context.settings().get_protocol_and_hostname()); let sender_name = &local_user_view.person.name; + let content = markdown_to_html(&content); send_email_to_user( &local_recipient, &lang.notification_private_message_subject(sender_name), diff --git a/crates/api_crud/src/private_message/update.rs b/crates/api_crud/src/private_message/update.rs index 7e55c6416..9e3b7c6b3 100644 --- a/crates/api_crud/src/private_message/update.rs +++ b/crates/api_crud/src/private_message/update.rs @@ -4,7 +4,7 @@ use lemmy_api_common::{ context::LemmyContext, private_message::{EditPrivateMessage, PrivateMessageResponse}, send_activity::{ActivityChannel, SendActivityData}, - utils::{local_site_to_slur_regex, sanitize_html_api}, + utils::local_site_to_slur_regex, }; use lemmy_db_schema::{ source::{ @@ -36,8 +36,7 @@ pub async fn update_private_message( } // Doing the update - let content = sanitize_html_api(&data.content); - let content = remove_slurs(&content, &local_site_to_slur_regex(&local_site)); + let content = remove_slurs(&data.content, &local_site_to_slur_regex(&local_site)); is_valid_body_field(&Some(content.clone()), false)?; let private_message_id = data.private_message_id; diff --git a/crates/api_crud/src/site/create.rs b/crates/api_crud/src/site/create.rs index 136a187f2..61dfd7c77 100644 --- a/crates/api_crud/src/site/create.rs +++ b/crates/api_crud/src/site/create.rs @@ -4,13 +4,7 @@ use actix_web::web::{Data, Json}; use lemmy_api_common::{ context::LemmyContext, site::{CreateSite, SiteResponse}, - utils::{ - generate_site_inbox_url, - is_admin, - local_site_rate_limit_to_rate_limit_config, - sanitize_html_api, - sanitize_html_api_opt, - }, + utils::{generate_site_inbox_url, is_admin, local_site_rate_limit_to_rate_limit_config}, }; use lemmy_db_schema::{ newtypes::DbUrl, @@ -55,14 +49,11 @@ pub async fn create_site( let actor_id: DbUrl = Url::parse(&context.settings().get_protocol_and_hostname())?.into(); let inbox_url = Some(generate_site_inbox_url(&actor_id)?); let keypair = generate_actor_keypair()?; - let name = sanitize_html_api(&data.name); - let sidebar = sanitize_html_api_opt(&data.sidebar); - let description = sanitize_html_api_opt(&data.description); let site_form = SiteUpdateForm { - name: Some(name), - sidebar: diesel_option_overwrite(sidebar), - description: diesel_option_overwrite(description), + name: Some(data.name.clone()), + sidebar: diesel_option_overwrite(data.sidebar.clone()), + description: diesel_option_overwrite(data.description.clone()), icon: diesel_option_overwrite_to_url(&data.icon)?, banner: diesel_option_overwrite_to_url(&data.banner)?, actor_id: Some(actor_id), @@ -77,10 +68,6 @@ pub async fn create_site( Site::update(&mut context.pool(), site_id, &site_form).await?; - let application_question = sanitize_html_api_opt(&data.application_question); - let default_theme = sanitize_html_api_opt(&data.default_theme); - let legal_information = sanitize_html_api_opt(&data.legal_information); - let local_site_form = LocalSiteUpdateForm { // Set the site setup to true site_setup: Some(true), @@ -89,11 +76,11 @@ pub async fn create_site( enable_nsfw: data.enable_nsfw, community_creation_admin_only: data.community_creation_admin_only, require_email_verification: data.require_email_verification, - application_question: diesel_option_overwrite(application_question), + application_question: diesel_option_overwrite(data.application_question.clone()), private_instance: data.private_instance, - default_theme, + default_theme: data.default_theme.clone(), default_post_listing_type: data.default_post_listing_type, - legal_information: diesel_option_overwrite(legal_information), + legal_information: diesel_option_overwrite(data.legal_information.clone()), application_email_admins: data.application_email_admins, hide_modlog_mod_names: data.hide_modlog_mod_names, updated: Some(Some(naive_now())), diff --git a/crates/api_crud/src/site/update.rs b/crates/api_crud/src/site/update.rs index 792faa785..3afc79559 100644 --- a/crates/api_crud/src/site/update.rs +++ b/crates/api_crud/src/site/update.rs @@ -3,7 +3,7 @@ use actix_web::web::{Data, Json}; use lemmy_api_common::{ context::LemmyContext, site::{EditSite, SiteResponse}, - utils::{is_admin, local_site_rate_limit_to_rate_limit_config, sanitize_html_api_opt}, + utils::{is_admin, local_site_rate_limit_to_rate_limit_config}, }; use lemmy_db_schema::{ source::{ @@ -54,14 +54,10 @@ pub async fn update_site( SiteLanguage::update(&mut context.pool(), discussion_languages.clone(), &site).await?; } - let name = sanitize_html_api_opt(&data.name); - let sidebar = sanitize_html_api_opt(&data.sidebar); - let description = sanitize_html_api_opt(&data.description); - let site_form = SiteUpdateForm { - name, - sidebar: diesel_option_overwrite(sidebar), - description: diesel_option_overwrite(description), + name: data.name.clone(), + sidebar: diesel_option_overwrite(data.sidebar.clone()), + description: diesel_option_overwrite(data.description.clone()), icon: diesel_option_overwrite_to_url(&data.icon)?, banner: diesel_option_overwrite_to_url(&data.banner)?, updated: Some(Some(naive_now())), @@ -74,21 +70,17 @@ pub async fn update_site( // Diesel will throw an error for empty update forms .ok(); - let application_question = sanitize_html_api_opt(&data.application_question); - let default_theme = sanitize_html_api_opt(&data.default_theme); - let legal_information = sanitize_html_api_opt(&data.legal_information); - let local_site_form = LocalSiteUpdateForm { enable_downvotes: data.enable_downvotes, registration_mode: data.registration_mode, enable_nsfw: data.enable_nsfw, community_creation_admin_only: data.community_creation_admin_only, require_email_verification: data.require_email_verification, - application_question: diesel_option_overwrite(application_question), + application_question: diesel_option_overwrite(data.application_question.clone()), private_instance: data.private_instance, - default_theme, + default_theme: data.default_theme.clone(), default_post_listing_type: data.default_post_listing_type, - legal_information: diesel_option_overwrite(legal_information), + legal_information: diesel_option_overwrite(data.legal_information.clone()), application_email_admins: data.application_email_admins, hide_modlog_mod_names: data.hide_modlog_mod_names, updated: Some(Some(naive_now())), diff --git a/crates/api_crud/src/user/create.rs b/crates/api_crud/src/user/create.rs index e9ac6d5cb..4a326a3ac 100644 --- a/crates/api_crud/src/user/create.rs +++ b/crates/api_crud/src/user/create.rs @@ -12,8 +12,6 @@ use lemmy_api_common::{ honeypot_check, local_site_to_slur_regex, password_length_check, - sanitize_html_api, - sanitize_html_api_opt, send_new_applicant_email_to_admins, send_verification_email, EndpointType, @@ -93,12 +91,6 @@ pub async fn register( check_slurs(&data.username, &slur_regex)?; check_slurs_opt(&data.answer, &slur_regex)?; - if sanitize_html_api(&data.username) != data.username { - Err(LemmyErrorType::InvalidName)?; - } - - let answer = sanitize_html_api_opt(&data.answer); - let actor_keypair = generate_actor_keypair()?; is_valid_actor_name(&data.username, local_site.actor_name_max_length as usize)?; let actor_id = generate_local_apub_endpoint( @@ -154,7 +146,7 @@ pub async fn register( let form = RegistrationApplicationInsertForm { local_user_id: inserted_local_user.id, // We already made sure answer was not null above - answer: answer.expect("must have an answer"), + answer: data.answer.clone().expect("must have an answer"), }; RegistrationApplication::create(&mut context.pool(), &form).await?; diff --git a/crates/apub/src/activities/block/block_user.rs b/crates/apub/src/activities/block/block_user.rs index 2d1f760c2..4469be53e 100644 --- a/crates/apub/src/activities/block/block_user.rs +++ b/crates/apub/src/activities/block/block_user.rs @@ -23,7 +23,7 @@ use anyhow::anyhow; use chrono::{DateTime, Utc}; use lemmy_api_common::{ context::LemmyContext, - utils::{remove_user_data, remove_user_data_in_community, sanitize_html_federation_opt}, + utils::{remove_user_data, remove_user_data_in_community}, }; use lemmy_db_schema::{ source::{ @@ -173,7 +173,7 @@ impl ActivityHandler for BlockUser { let form = ModBanForm { mod_person_id: mod_person.id, other_person_id: blocked_person.id, - reason: sanitize_html_federation_opt(&self.summary), + reason: self.summary, banned: Some(true), expires, }; @@ -207,7 +207,7 @@ impl ActivityHandler for BlockUser { mod_person_id: mod_person.id, other_person_id: blocked_person.id, community_id: community.id, - reason: sanitize_html_federation_opt(&self.summary), + reason: self.summary, banned: Some(true), expires, }; diff --git a/crates/apub/src/activities/block/undo_block_user.rs b/crates/apub/src/activities/block/undo_block_user.rs index 0796f86e7..97e2bc336 100644 --- a/crates/apub/src/activities/block/undo_block_user.rs +++ b/crates/apub/src/activities/block/undo_block_user.rs @@ -17,7 +17,7 @@ use activitypub_federation::{ protocol::verification::verify_domains_match, traits::{ActivityHandler, Actor}, }; -use lemmy_api_common::{context::LemmyContext, utils::sanitize_html_federation_opt}; +use lemmy_api_common::context::LemmyContext; use lemmy_db_schema::{ source::{ activity::ActivitySendTargets, @@ -118,7 +118,7 @@ impl ActivityHandler for UndoBlockUser { let form = ModBanForm { mod_person_id: mod_person.id, other_person_id: blocked_person.id, - reason: sanitize_html_federation_opt(&self.object.summary), + reason: self.object.summary, banned: Some(false), expires, }; @@ -137,7 +137,7 @@ impl ActivityHandler for UndoBlockUser { mod_person_id: mod_person.id, other_person_id: blocked_person.id, community_id: community.id, - reason: sanitize_html_federation_opt(&self.object.summary), + reason: self.object.summary, banned: Some(false), expires, }; diff --git a/crates/apub/src/activities/community/report.rs b/crates/apub/src/activities/community/report.rs index d6c058448..7da5ac8ae 100644 --- a/crates/apub/src/activities/community/report.rs +++ b/crates/apub/src/activities/community/report.rs @@ -11,7 +11,7 @@ use activitypub_federation::{ kinds::activity::FlagType, traits::{ActivityHandler, Actor}, }; -use lemmy_api_common::{context::LemmyContext, utils::sanitize_html_federation}; +use lemmy_api_common::context::LemmyContext; use lemmy_db_schema::{ source::{ activity::ActivitySendTargets, @@ -90,7 +90,7 @@ impl ActivityHandler for Report { post_id: post.id, original_post_name: post.name.clone(), original_post_url: post.url.clone(), - reason: sanitize_html_federation(&self.summary), + reason: self.summary.clone(), original_post_body: post.body.clone(), }; PostReport::report(&mut context.pool(), &report_form).await?; @@ -100,7 +100,7 @@ impl ActivityHandler for Report { creator_id: actor.id, comment_id: comment.id, original_comment_text: comment.content.clone(), - reason: sanitize_html_federation(&self.summary), + reason: self.summary.clone(), }; CommentReport::report(&mut context.pool(), &report_form).await?; } diff --git a/crates/apub/src/activities/deletion/delete.rs b/crates/apub/src/activities/deletion/delete.rs index 88bf2b523..28c4eace7 100644 --- a/crates/apub/src/activities/deletion/delete.rs +++ b/crates/apub/src/activities/deletion/delete.rs @@ -8,7 +8,7 @@ use crate::{ protocol::{activities::deletion::delete::Delete, IdOrNestedObject}, }; use activitypub_federation::{config::Data, kinds::activity::DeleteType, traits::ActivityHandler}; -use lemmy_api_common::{context::LemmyContext, utils::sanitize_html_federation_opt}; +use lemmy_api_common::context::LemmyContext; use lemmy_db_schema::{ source::{ comment::{Comment, CommentUpdateForm}, @@ -105,8 +105,6 @@ pub(in crate::activities) async fn receive_remove_action( reason: Option, context: &Data, ) -> Result<(), LemmyError> { - let reason = sanitize_html_federation_opt(&reason); - match DeletableObjects::read_from_db(object, context).await? { DeletableObjects::Community(community) => { if community.local { diff --git a/crates/apub/src/objects/comment.rs b/crates/apub/src/objects/comment.rs index 599dd7387..4d57b50ee 100644 --- a/crates/apub/src/objects/comment.rs +++ b/crates/apub/src/objects/comment.rs @@ -16,10 +16,7 @@ use activitypub_federation::{ traits::Object, }; use chrono::{DateTime, Utc}; -use lemmy_api_common::{ - context::LemmyContext, - utils::{local_site_opt_to_slur_regex, sanitize_html_federation}, -}; +use lemmy_api_common::{context::LemmyContext, utils::local_site_opt_to_slur_regex}; use lemmy_db_schema::{ source::{ comment::{Comment, CommentInsertForm, CommentUpdateForm}, @@ -162,7 +159,6 @@ impl Object for ApubComment { let local_site = LocalSite::read(&mut context.pool()).await.ok(); let slur_regex = &local_site_opt_to_slur_regex(&local_site); let content = remove_slurs(&content, slur_regex); - let content = sanitize_html_federation(&content); let language_id = LanguageTag::to_language_id_single(note.language, &mut context.pool()).await?; diff --git a/crates/apub/src/objects/instance.rs b/crates/apub/src/objects/instance.rs index 5949e12bf..d6086fdc2 100644 --- a/crates/apub/src/objects/instance.rs +++ b/crates/apub/src/objects/instance.rs @@ -17,10 +17,7 @@ use activitypub_federation::{ traits::{Actor, Object}, }; use chrono::{DateTime, Utc}; -use lemmy_api_common::{ - context::LemmyContext, - utils::{local_site_opt_to_slur_regex, sanitize_html_federation_opt}, -}; +use lemmy_api_common::{context::LemmyContext, utils::local_site_opt_to_slur_regex}; use lemmy_db_schema::{ newtypes::InstanceId, source::{ @@ -135,8 +132,6 @@ impl Object for ApubSite { let instance = DbInstance::read_or_create(&mut data.pool(), domain.to_string()).await?; let sidebar = read_from_string_or_source_opt(&apub.content, &None, &apub.source); - let sidebar = sanitize_html_federation_opt(&sidebar); - let description = sanitize_html_federation_opt(&apub.summary); let site_form = SiteInsertForm { name: apub.name.clone(), @@ -144,7 +139,7 @@ impl Object for ApubSite { updated: apub.updated, icon: apub.icon.clone().map(|i| i.url.into()), banner: apub.image.clone().map(|i| i.url.into()), - description, + description: apub.summary, actor_id: Some(apub.id.clone().into()), last_refreshed_at: Some(naive_now()), inbox_url: Some(apub.inbox.clone().into()), diff --git a/crates/apub/src/objects/person.rs b/crates/apub/src/objects/person.rs index 34d824890..3ca473616 100644 --- a/crates/apub/src/objects/person.rs +++ b/crates/apub/src/objects/person.rs @@ -20,12 +20,7 @@ use activitypub_federation::{ use chrono::{DateTime, Utc}; use lemmy_api_common::{ context::LemmyContext, - utils::{ - generate_outbox_url, - local_site_opt_to_slur_regex, - sanitize_html_federation, - sanitize_html_federation_opt, - }, + utils::{generate_outbox_url, local_site_opt_to_slur_regex}, }; use lemmy_db_schema::{ source::{ @@ -150,17 +145,14 @@ impl Object for ApubPerson { ) -> Result { let instance_id = fetch_instance_actor_for_object(&person.id, context).await?; - let name = sanitize_html_federation(&person.preferred_username); - let display_name = sanitize_html_federation_opt(&person.name); let bio = read_from_string_or_source_opt(&person.summary, &None, &person.source); - let bio = sanitize_html_federation_opt(&bio); // Some Mastodon users have `name: ""` (empty string), need to convert that to `None` // https://github.com/mastodon/mastodon/issues/25233 - let display_name = display_name.filter(|n| !n.is_empty()); + let display_name = person.name.filter(|n| !n.is_empty()); let person_form = PersonInsertForm { - name, + name: person.preferred_username, display_name, banned: None, ban_expires: None, @@ -275,7 +267,7 @@ pub(crate) mod tests { assert_eq!(person.name, "lanodan"); assert!(!person.local); assert_eq!(context.request_count(), 0); - assert_eq!(person.bio.as_ref().unwrap().len(), 878); + assert_eq!(person.bio.as_ref().unwrap().len(), 873); cleanup((person, site), &context).await; } diff --git a/crates/apub/src/objects/post.rs b/crates/apub/src/objects/post.rs index 2fc859ddf..c25f988dc 100644 --- a/crates/apub/src/objects/post.rs +++ b/crates/apub/src/objects/post.rs @@ -25,13 +25,7 @@ use html2md::parse_html; use lemmy_api_common::{ context::LemmyContext, request::fetch_site_data, - utils::{ - is_mod_or_admin, - local_site_opt_to_sensitive, - local_site_opt_to_slur_regex, - sanitize_html_federation, - sanitize_html_federation_opt, - }, + utils::{is_mod_or_admin, local_site_opt_to_sensitive, local_site_opt_to_slur_regex}, }; use lemmy_db_schema::{ self, @@ -231,17 +225,11 @@ impl Object for ApubPost { .unwrap_or_default(); let slur_regex = &local_site_opt_to_slur_regex(&local_site); - let body_slurs_removed = - read_from_string_or_source_opt(&page.content, &page.media_type, &page.source) - .map(|s| remove_slurs(&s, slur_regex)); + let body = read_from_string_or_source_opt(&page.content, &page.media_type, &page.source) + .map(|s| remove_slurs(&s, slur_regex)); let language_id = LanguageTag::to_language_id_single(page.language, &mut context.pool()).await?; - let name = sanitize_html_federation(&name); - let body = sanitize_html_federation_opt(&body_slurs_removed); - let embed_title = sanitize_html_federation_opt(&embed_title); - let embed_description = sanitize_html_federation_opt(&embed_description); - PostInsertForm { name, url: url.map(Into::into), diff --git a/crates/apub/src/objects/private_message.rs b/crates/apub/src/objects/private_message.rs index 97bf595d5..f683a989f 100644 --- a/crates/apub/src/objects/private_message.rs +++ b/crates/apub/src/objects/private_message.rs @@ -12,10 +12,7 @@ use activitypub_federation::{ traits::Object, }; use chrono::{DateTime, Utc}; -use lemmy_api_common::{ - context::LemmyContext, - utils::{check_person_block, sanitize_html_federation}, -}; +use lemmy_api_common::{context::LemmyContext, utils::check_person_block}; use lemmy_db_schema::{ source::{ person::Person, @@ -125,7 +122,6 @@ impl Object for ApubPrivateMessage { check_person_block(creator.id, recipient.id, &mut context.pool()).await?; let content = read_from_string_or_source(¬e.content, &None, ¬e.source); - let content = sanitize_html_federation(&content); let form = PrivateMessageInsertForm { creator_id: creator.id, diff --git a/crates/apub/src/protocol/objects/group.rs b/crates/apub/src/protocol/objects/group.rs index 9d0229785..ab14ef6ea 100644 --- a/crates/apub/src/protocol/objects/group.rs +++ b/crates/apub/src/protocol/objects/group.rs @@ -23,10 +23,7 @@ use activitypub_federation::{ }, }; use chrono::{DateTime, Utc}; -use lemmy_api_common::{ - context::LemmyContext, - utils::{local_site_opt_to_slur_regex, sanitize_html_federation, sanitize_html_federation_opt}, -}; +use lemmy_api_common::{context::LemmyContext, utils::local_site_opt_to_slur_regex}; use lemmy_db_schema::{ newtypes::InstanceId, source::community::{CommunityInsertForm, CommunityUpdateForm}, @@ -97,14 +94,11 @@ impl Group { } pub(crate) fn into_insert_form(self, instance_id: InstanceId) -> CommunityInsertForm { - let name = sanitize_html_federation(&self.preferred_username); - let title = sanitize_html_federation(&self.name.unwrap_or(self.preferred_username)); let description = read_from_string_or_source_opt(&self.summary, &None, &self.source); - let description = sanitize_html_federation_opt(&description); CommunityInsertForm { - name, - title, + name: self.preferred_username.clone(), + title: self.name.unwrap_or(self.preferred_username.clone()), description, removed: None, published: self.published, diff --git a/crates/routes/src/feeds.rs b/crates/routes/src/feeds.rs index 446db86ea..3ef760a51 100644 --- a/crates/routes/src/feeds.rs +++ b/crates/routes/src/feeds.rs @@ -22,7 +22,7 @@ use lemmy_db_views_actor::{ use lemmy_utils::{ cache_header::cache_1hour, error::LemmyError, - utils::markdown::markdown_to_html, + utils::markdown::{markdown_to_html, sanitize_html}, }; use once_cell::sync::Lazy; use rss::{ @@ -289,7 +289,7 @@ async fn get_feed_community( .items(items); if let Some(community_desc) = community.description { - channel_builder.description(&community_desc); + channel_builder.description(markdown_to_html(&community_desc)); } Ok(channel_builder) @@ -328,7 +328,7 @@ async fn get_feed_front( .items(items); if let Some(site_desc) = site_view.site.description { - channel_builder.description(&site_desc); + channel_builder.description(markdown_to_html(&site_desc)); } Ok(channel_builder) @@ -457,7 +457,7 @@ fn create_post_items( let mut i = ItemBuilder::default(); let mut dc_extension = DublinCoreExtensionBuilder::default(); - i.title(p.post.name); + i.title(sanitize_html(&p.post.name)); dc_extension.creators(vec![p.creator.actor_id.to_string()]); @@ -472,14 +472,18 @@ fn create_post_items( .build(); i.guid(guid); - let community_url = format!("{}/c/{}", protocol_and_hostname, p.community.name); + let community_url = format!( + "{}/c/{}", + protocol_and_hostname, + sanitize_html(&p.community.name) + ); // TODO add images let mut description = format!("submitted by {} to {}
{} points | {} comments", p.creator.actor_id, - p.creator.name, + sanitize_html(&p.creator.name), community_url, - p.community.name, + sanitize_html(&p.community.name), p.counts.score, post_url, p.counts.comments); diff --git a/crates/utils/src/utils/markdown.rs b/crates/utils/src/utils/markdown.rs index 5f851589b..a051310ca 100644 --- a/crates/utils/src/utils/markdown.rs +++ b/crates/utils/src/utils/markdown.rs @@ -12,6 +12,20 @@ static MARKDOWN_PARSER: Lazy = Lazy::new(|| { parser }); +/// Replace special HTML characters in API parameters to prevent XSS attacks. +/// +/// Taken from https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.md#output-encoding-for-html-contexts +/// +/// `>` is left in place because it is interpreted as markdown quote. +pub fn sanitize_html(text: &str) -> String { + text + .replace('&', "&") + .replace('<', "<") + .replace('\"', """) + .replace('\'', "'") +} + +/// Converts text from markdown to HTML, while escaping special characters. pub fn markdown_to_html(text: &str) -> String { MARKDOWN_PARSER.parse(text).xrender() } @@ -21,7 +35,7 @@ mod tests { #![allow(clippy::unwrap_used)] #![allow(clippy::indexing_slicing)] - use crate::utils::markdown::markdown_to_html; + use super::*; #[test] fn test_basic_markdown() { @@ -71,6 +85,11 @@ mod tests { "::: spoiler click to see more\nhow spicy!\n:::\n", "
click to see more

how spicy!\n

\n" ), + ( + "escape html special chars", + " hello &\"", + "

<script>alert(‘xss’);</script> hello &"

\n" + ) ]; tests.iter().for_each(|&(msg, input, expected)| { @@ -83,4 +102,11 @@ mod tests { ); }); } + + #[test] + fn test_sanitize_html() { + let sanitized = sanitize_html(" hello &\"'"); + let expected = "<script>alert('xss');</script> hello &"'"; + assert_eq!(expected, sanitized) + } } diff --git a/crates/utils/translations b/crates/utils/translations index 18da10858..e943f97fe 160000 --- a/crates/utils/translations +++ b/crates/utils/translations @@ -1 +1 @@ -Subproject commit 18da10858d8c63750beb06247947f25d91944741 +Subproject commit e943f97fe481dc425acdebc8872bf1fdcabaf875 From 3a1be8c041970b98ea6f3ea9aa71b0f8024aaf19 Mon Sep 17 00:00:00 2001 From: Maxime Poulin Date: Wed, 11 Oct 2023 10:53:18 -0400 Subject: [PATCH 03/24] Fix legacy pagination for Subscribed feed (#4030) This is the fix suggested in #4019 I've manually tested both legacy pagination (`page=2&limit=20`) as well as the new pagination (`cursor_next=XXXXXXX&limit=20`) using the same endpoint as #4019, and both methods appear to work as expected. --- crates/db_views/src/post_view.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/db_views/src/post_view.rs b/crates/db_views/src/post_view.rs index 87c31d7e5..82e830ac1 100644 --- a/crates/db_views/src/post_view.rs +++ b/crates/db_views/src/post_view.rs @@ -629,7 +629,7 @@ impl<'a> PostQuery<'a> { }, }; let (limit, offset) = limit_and_offset(self.page, self.limit)?; - if offset != 0 { + if offset != 0 && self.page_after.is_some() { return Err(Error::QueryBuilderError( "legacy pagination cannot be combined with v2 pagination".into(), )); From 645bf21d54433022983dd883699103207a60941a Mon Sep 17 00:00:00 2001 From: Dessalines Date: Wed, 11 Oct 2023 11:57:05 -0400 Subject: [PATCH 04/24] Moving to debian-based docker image. Fixes #3972 (#4004) * Moving to debian-based docker image. Fixes #3972 * Fix cargo.toml format. * Try nightly rust for cargo_fmt. * Trying to fix cargo build. * Trying to fix cargo build 2. * Trying to fix federation tests. * Trying to fix federation tests 2. * Trying to fix federation tests 3. * Try to fix aarch 1. * Try to fix aarch 2. * Try to fix aarch 3. * Removing aarch * Adding tests back in. * Adding ca-certs, and debian non-root user * Commenting arm image building. * Fix platform on runner. * Version 0.19.0-beta.6 --- .woodpecker.yml | 32 +++++----- Cargo.toml | 2 + docker/Dockerfile | 160 +++++++++++++++++++++------------------------- 3 files changed, 91 insertions(+), 103 deletions(-) diff --git a/.woodpecker.yml b/.woodpecker.yml index 32fa9a9da..ff4685ac2 100644 --- a/.woodpecker.yml +++ b/.woodpecker.yml @@ -2,7 +2,7 @@ # See https://github.com/woodpecker-ci/woodpecker/issues/1677 variables: - - &muslrust_image "clux/muslrust:1.70.0" + - &rust_image "rust:1.72.1" - &slow_check_paths - path: # rust source code @@ -57,15 +57,13 @@ steps: cargo_fmt: group: format - image: *muslrust_image + image: rustlang/rust:nightly environment: # store cargo data in repo folder so that it gets cached between steps CARGO_HOME: .cargo commands: # need make existing toolchain available - - cp -n ~/.cargo . -r - - rustup toolchain install nightly-2023-07-10 --no-self-update --profile minimal --component rustfmt - - cargo +nightly-2023-07-10 fmt -- --check + - cargo +nightly fmt -- --check restore-cache: image: meltwater/drone-cache:v1 @@ -93,7 +91,7 @@ steps: # make sure api builds with default features (used by other crates relying on lemmy api) check_api_common_default_features: - image: *muslrust_image + image: *rust_image environment: CARGO_HOME: .cargo commands: @@ -101,7 +99,7 @@ steps: when: *slow_check_paths lemmy_api_common_doesnt_depend_on_diesel: - image: *muslrust_image + image: *rust_image environment: CARGO_HOME: .cargo commands: @@ -109,7 +107,7 @@ steps: when: *slow_check_paths lemmy_api_common_works_with_wasm: - image: *muslrust_image + image: *rust_image environment: CARGO_HOME: .cargo commands: @@ -118,7 +116,7 @@ steps: when: *slow_check_paths check_defaults_hjson_updated: - image: *muslrust_image + image: *rust_image environment: CARGO_HOME: .cargo commands: @@ -149,7 +147,7 @@ steps: when: *slow_check_paths cargo_clippy: - image: *muslrust_image + image: *rust_image environment: CARGO_HOME: .cargo commands: @@ -173,17 +171,17 @@ steps: when: *slow_check_paths cargo_build: - image: *muslrust_image + image: *rust_image environment: CARGO_HOME: .cargo commands: - cargo build - - mv target/x86_64-unknown-linux-musl/debug/lemmy_server target/lemmy_server + - mv target/debug/lemmy_server target/lemmy_server when: *slow_check_paths cargo_test: group: tests - image: *muslrust_image + image: *rust_image environment: LEMMY_DATABASE_URL: postgres://lemmy:password@database:5432/lemmy RUST_BACKTRACE: "1" @@ -195,12 +193,12 @@ steps: run_federation_tests: group: tests - image: node:alpine + image: node:20-bookworm-slim environment: LEMMY_DATABASE_URL: postgres://lemmy:password@database:5432 DO_WRITE_HOSTS_FILE: "1" commands: - - apk add bash curl postgresql-client + - apt update && apt install -y bash curl postgresql-client - bash api_tests/prepare-drone-federation-test.sh - cd api_tests/ - yarn @@ -239,7 +237,9 @@ steps: settings: repo: dessalines/lemmy dockerfile: docker/Dockerfile - platforms: linux/amd64,linux/arm64 + # TODO fix arm build: see: https://woodpecker.join-lemmy.org/repos/129/pipeline/2888/20 + # platforms: linux/amd64,linux/arm64 + platforms: linux/amd64 build_args: - RUST_RELEASE_MODE=release tag: ${CI_COMMIT_TAG} diff --git a/Cargo.toml b/Cargo.toml index 5e5cc654f..9c3fa787e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,8 @@ doctest = false [profile.release] debug = 0 lto = "thin" +strip = true # Automatically strip symbols from the binary. +opt-level = "z" # Optimize for size. # This profile significantly speeds up build time. If debug info is needed you can comment the line # out temporarily, but make sure to leave this in the main branch. diff --git a/docker/Dockerfile b/docker/Dockerfile index 9f21aaf9b..eab612598 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,54 +1,13 @@ -# -# Docker multiarch image: -# We build the Lemmy binary for amd64 and arm64 in individual stages using the blackdex/rust-musl image (github.com/blackdex/rust-musl). -# This image uses musl-cross-make (github.com/richfelker/musl-cross-make) to build a musl cross compilation toolchain for the target -# architecture. It also includes pre-built static libraries such as libpq. These libraries can improve the compile time and eliminate -# the requirement for extra dependencies in the final image. -# -# During each build stage, we use the blackdex/rust-musl openssl 3 images and configure PQ_LIB_DIR=/usr/local/musl/pq15/lib to use -# libpq v15. We also ensure the installation of the Rust toolchain corresponding to the target architecture using: -# `rustup target add $TARGET-unknown-linux-musl`. -# - -ARG RUST_VERSION=1.71.0 -ARG ALPINE_VERSION=3.18 +ARG RUST_VERSION=1.72.1 ARG CARGO_BUILD_FEATURES=default ARG RUST_RELEASE_MODE=debug -ARG UID=911 -ARG GID=911 - -# AMD64 builder base -FROM --platform=${BUILDPLATFORM} blackdex/rust-musl:x86_64-musl-stable-${RUST_VERSION}-openssl3 AS base-amd64 - -ENV DEBIAN_FRONTEND=noninteractive -ENV CARGO_HOME=/root/.cargo -ENV PQ_LIB_DIR=/usr/local/musl/pq15/lib - -RUN apt update && apt install -y \ - --no-install-recommends \ - git - -RUN mkdir -pv "${CARGO_HOME}" && \ - rustup set profile minimal && \ - rustup target add x86_64-unknown-linux-musl - -# ARM64 builder base -FROM --platform=${BUILDPLATFORM} blackdex/rust-musl:aarch64-musl-stable-${RUST_VERSION}-openssl3 AS base-arm64 - -ENV DEBIAN_FRONTEND=noninteractive -ENV CARGO_HOME=/root/.cargo -ENV PQ_LIB_DIR=/usr/local/musl/pq15/lib - -RUN apt update && apt install -y \ - --no-install-recommends \ - git - -RUN mkdir -pv "${CARGO_HOME}" && \ - rustup set profile minimal && \ - rustup target add aarch64-unknown-linux-musl +ARG AMD_BUILDER_IMAGE=rust:${RUST_VERSION} +ARG ARM_BUILDER_IMAGE=blackdex/rust-musl:aarch64-musl-stable-${RUST_VERSION}-openssl3 +ARG AMD_RUNNER_IMAGE=debian:bookworm-slim +ARG ARM_RUNNER_IMAGE=alpine:3.18 # AMD64 builder -FROM base-amd64 AS build-amd64 +FROM --platform=${BUILDPLATFORM} ${AMD_BUILDER_IMAGE} AS build-amd64 ARG CARGO_BUILD_FEATURES ARG RUST_RELEASE_MODE @@ -61,65 +20,92 @@ COPY . ./ RUN --mount=type=cache,target=/lemmy/target set -ex; \ if [ "${RUST_RELEASE_MODE}" = "debug" ]; then \ echo "pub const VERSION: &str = \"$(git describe --tag)\";" > crates/utils/src/version.rs; \ - cargo build --target=x86_64-unknown-linux-musl --features "${CARGO_BUILD_FEATURES}"; \ - mv target/x86_64-unknown-linux-musl/debug/lemmy_server ./lemmy; \ + cargo build --features "${CARGO_BUILD_FEATURES}"; \ + mv target/debug/lemmy_server ./lemmy; \ fi # Release build RUN set -ex; \ if [ "${RUST_RELEASE_MODE}" = "release" ]; then \ echo "pub const VERSION: &str = \"$(git describe --tag)\";" > crates/utils/src/version.rs; \ - cargo build --target=x86_64-unknown-linux-musl --features "${CARGO_BUILD_FEATURES}" --release; \ - mv target/x86_64-unknown-linux-musl/release/lemmy_server ./lemmy; \ + cargo build --features "${CARGO_BUILD_FEATURES}" --release; \ + mv target/release/lemmy_server ./lemmy; \ fi # ARM64 builder -FROM base-arm64 AS build-arm64 +# TODO currently broken +# FROM --platform=${BUILDPLATFORM} ${ARM_BUILDER_IMAGE} as build-arm64 -ARG CARGO_BUILD_FEATURES -ARG RUST_RELEASE_MODE +# ENV DEBIAN_FRONTEND=noninteractive +# ENV CARGO_HOME=/root/.cargo +# ENV PQ_LIB_DIR=/usr/local/musl/pq15/lib -WORKDIR /lemmy +# RUN apt update && apt install -y \ +# --no-install-recommends \ +# git -COPY . ./ +# RUN mkdir -pv "${CARGO_HOME}" && \ +# rustup set profile minimal && \ +# rustup target add aarch64-unknown-linux-musl -# Debug build -RUN --mount=type=cache,target=/lemmy/target set -ex; \ - if [ "${RUST_RELEASE_MODE}" = "debug" ]; then \ - echo "pub const VERSION: &str = \"$(git describe --tag)\";" > crates/utils/src/version.rs; \ - cargo build --target=aarch64-unknown-linux-musl --features "${CARGO_BUILD_FEATURES}"; \ - mv target/aarch64-unknown-linux-musl/debug/lemmy_server ./lemmy; \ - fi +# ARG CARGO_BUILD_FEATURES +# ARG RUST_RELEASE_MODE -# Release build -RUN set -ex; \ - if [ "${RUST_RELEASE_MODE}" = "release" ]; then \ - echo "pub const VERSION: &str = \"$(git describe --tag)\";" > crates/utils/src/version.rs; \ - cargo build --target=aarch64-unknown-linux-musl --features "${CARGO_BUILD_FEATURES}" --release; \ - mv target/aarch64-unknown-linux-musl/release/lemmy_server ./lemmy; \ - fi +# WORKDIR /lemmy -# Get target binary -FROM build-${TARGETARCH} AS build +# COPY . ./ + +# # Debug build +# RUN --mount=type=cache,target=/lemmy/target set -ex; \ +# if [ "${RUST_RELEASE_MODE}" = "debug" ]; then \ +# echo "pub const VERSION: &str = \"$(git describe --tag)\";" > crates/utils/src/version.rs; \ +# cargo build --target=aarch64-unknown-linux-musl --features "${CARGO_BUILD_FEATURES}"; \ +# mv target/aarch64-unknown-linux-musl/debug/lemmy_server ./lemmy; \ +# fi + +# # Release build +# RUN set -ex; \ +# if [ "${RUST_RELEASE_MODE}" = "release" ]; then \ +# echo "pub const VERSION: &str = \"$(git describe --tag)\";" > crates/utils/src/version.rs; \ +# cargo build --target=aarch64-unknown-linux-musl --features "${CARGO_BUILD_FEATURES}" --release; \ +# mv target/aarch64-unknown-linux-musl/release/lemmy_server ./lemmy; \ +# fi ## Final image -FROM alpine:${ALPINE_VERSION} +FROM ${AMD_RUNNER_IMAGE} -ARG UID -ARG GID +# Federation needs CA certificates +RUN apt update && apt install -y libssl-dev libpq-dev ca-certificates -RUN apk add --no-cache \ - ca-certificates - -COPY --from=build --chmod=0755 /lemmy/lemmy /usr/local/bin - -RUN addgroup -S -g ${GID} lemmy && \ - adduser -S -H -D -G lemmy -u ${UID} -g "" -s /sbin/nologin lemmy - -USER lemmy - -CMD ["lemmy"] +# Debian / Ubuntu non-root user creds +ARG UNAME=lemmy +ARG UID=1000 +ARG GID=1000 +RUN groupadd -g $GID -o $UNAME +RUN useradd -m -u $UID -g $GID -o -s /bin/bash $UNAME +USER $UNAME +COPY --from=build-amd64 /lemmy/lemmy ./ +CMD ["./lemmy"] EXPOSE 8536 +STOPSIGNAL SIGTERM + +## Arm Runner +# FROM --platform=${BUILDPLATFORM} ${ARM_RUNNER_IMAGE} + +# ARG UNAME=lemmy +# ARG UID=1000 +# ARG GID=1000 + +# RUN apk add --no-cache ca-certificates + +# COPY --from=build-arm64 --chmod=0755 /lemmy/lemmy /usr/local/bin + +# RUN addgroup -S -g ${GID} ${UNAME} && \ +# adduser -S -H -D -G ${UNAME} -u ${UID} -g "" -s /sbin/nologin ${UNAME} +# USER $UNAME + +# CMD ["lemmy"] +# EXPOSE 8536 +# STOPSIGNAL SIGTERM -STOPSIGNAL SIGTERM \ No newline at end of file From 608bb6b1b4099fe0d7a4c3fad118a4cd65226d72 Mon Sep 17 00:00:00 2001 From: Nutomic Date: Fri, 13 Oct 2023 02:36:02 +0200 Subject: [PATCH 05/24] Convert titles for posts from Mastodon to plaintext (fixes #3828) (#4033) * Convert titles for posts from Mastodon to plaintext (fixes #3828) * Fix prettier. * Trigger build * Convert titles for posts from Mastodon to plaintext (fixes #3828) * Fix prettier. * Fix sanizize. --------- Co-authored-by: Dessalines Co-authored-by: Dessalines --- Cargo.lock | 8 +++ crates/apub/Cargo.toml | 2 + crates/apub/assets/lemmy/objects/group.json | 2 +- crates/apub/assets/mastodon/objects/page.json | 36 +++++++------- crates/apub/src/api/user_settings_backup.rs | 7 +-- crates/apub/src/objects/post.rs | 49 ++++++++++++++++--- 6 files changed, 77 insertions(+), 27 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 41f132612..36bbfce8e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2719,6 +2719,7 @@ dependencies = [ "enum_delegate", "futures", "html2md", + "html2text", "http", "itertools 0.11.0", "lemmy_api_common", @@ -2734,6 +2735,7 @@ dependencies = [ "serde_json", "serde_with", "serial_test", + "stringreader", "strum_macros", "task-local-extensions", "tokio", @@ -4950,6 +4952,12 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "stringreader" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "913e7b03d63752f6cdd2df77da36749d82669904798fe8944b9ec3d23f159905" + [[package]] name = "strsim" version = "0.10.0" diff --git a/crates/apub/Cargo.toml b/crates/apub/Cargo.toml index ebdf3245e..748fe3335 100644 --- a/crates/apub/Cargo.toml +++ b/crates/apub/Cargo.toml @@ -38,6 +38,8 @@ anyhow = { workspace = true } reqwest = { workspace = true } once_cell = { workspace = true } html2md = "0.2.14" +html2text = "0.6.0" +stringreader = "0.1.1" serde_with = { workspace = true } enum_delegate = "0.2.0" moka = { version = "0.11", features = ["future"] } diff --git a/crates/apub/assets/lemmy/objects/group.json b/crates/apub/assets/lemmy/objects/group.json index 3870daf75..1b848a866 100644 --- a/crates/apub/assets/lemmy/objects/group.json +++ b/crates/apub/assets/lemmy/objects/group.json @@ -1,7 +1,7 @@ { "id": "https://enterprise.lemmy.ml/c/tenforward", "type": "Group", - "preferredUsername": "main", + "preferredUsername": "tenforward", "name": "Ten Forward", "summary": "

Lounge and recreation facility

\n
\n

Welcome to the Enterprise!.

\n", "source": { diff --git a/crates/apub/assets/mastodon/objects/page.json b/crates/apub/assets/mastodon/objects/page.json index d2195dbf3..ec4c13080 100644 --- a/crates/apub/assets/mastodon/objects/page.json +++ b/crates/apub/assets/mastodon/objects/page.json @@ -11,40 +11,42 @@ "votersCount": "toot:votersCount" } ], - "id": "https://mastodon.madrid/users/felix/statuses/107224289116410645", + "id": "https://dice.camp/users/thekernelinyellow/statuses/110830743680706519", "type": "Note", "summary": null, - "published": "2021-11-05T11:46:50Z", - "url": "https://mastodon.madrid/@felix/107224289116410645", - "attributedTo": "https://mastodon.madrid/users/felix", - "to": ["https://mastodon.madrid/users/felix/followers"], + "inReplyTo": null, + "published": "2023-08-04T09:55:39Z", + "url": "https://dice.camp/@thekernelinyellow/110830743680706519", + "attributedTo": "https://dice.camp/users/thekernelinyellow", + "to": ["https://www.w3.org/ns/activitystreams#Public"], "cc": [ - "https://www.w3.org/ns/activitystreams#Public", - "https://mamot.fr/users/retiolus" + "https://dice.camp/users/thekernelinyellow/followers", + "https://enterprise.lemmy.ml/c/tenforward", + "https://enterprise.lemmy.ml/c/tenforward/followers" ], "sensitive": false, - "atomUri": "https://mastodon.madrid/users/felix/statuses/107224289116410645", - "inReplyToAtomUri": "https://mamot.fr/users/retiolus/statuses/107224244380204526", - "conversation": "tag:mamot.fr,2021-11-05:objectId=64635960:objectType=Conversation", - "content": "

@retiolus i have never been disappointed by a thinkpad. if you want to save money, get a model from a few years ago, there isnt a huge difference anyway.

", + "atomUri": "https://dice.camp/users/thekernelinyellow/statuses/110830743680706519", + "inReplyToAtomUri": null, + "conversation": "tag:dice.camp,2023-08-04:objectId=29969291:objectType=Conversation", + "content": "

@tenforward Variable never resetting at refresh

Hi! I'm using a variable to count elements in my generator but every time I generate a new character, the counter's value carries on from the previous one. Is there a function to reset it (I set it to 0 at the beginning of the file)

", "contentMap": { - "en": "

@retiolus i have never been disappointed by a thinkpad. if you want to save money, get a model from a few years ago, there isnt a huge difference anyway.

" + "it": "

@tenforwardVariable never resetting at refresh

Hi! I'm using a variable to count elements in my generator but every time I generate a new character, the counter's value carries on from the previous one. Is there a function to reset it (I set it to 0 at the beginning of the file)

" }, "attachment": [], "tag": [ { "type": "Mention", - "href": "https://mamot.fr/users/retiolus", - "name": "@retiolus@mamot.fr" + "href": "https://enterprise.lemmy.ml/c/tenforward", + "name": "@tenforward@enterprise.lemmy.ml" } ], "replies": { - "id": "https://mastodon.madrid/users/felix/statuses/107224289116410645/replies", + "id": "https://dice.camp/users/thekernelinyellow/statuses/110830743680706519/replies", "type": "Collection", "first": { "type": "CollectionPage", - "next": "https://mastodon.madrid/users/felix/statuses/107224289116410645/replies?only_other_accounts=true&page=true", - "partOf": "https://mastodon.madrid/users/felix/statuses/107224289116410645/replies", + "next": "https://dice.camp/users/thekernelinyellow/statuses/110830743680706519/replies?only_other_accounts=true&page=true", + "partOf": "https://dice.camp/users/thekernelinyellow/statuses/110830743680706519/replies", "items": [] } } diff --git a/crates/apub/src/api/user_settings_backup.rs b/crates/apub/src/api/user_settings_backup.rs index 0349171a9..c1102791d 100644 --- a/crates/apub/src/api/user_settings_backup.rs +++ b/crates/apub/src/api/user_settings_backup.rs @@ -7,7 +7,7 @@ use crate::objects::{ use activitypub_federation::{config::Data, fetch::object_id::ObjectId}; use actix_web::web::Json; use futures::{future::try_join_all, StreamExt}; -use lemmy_api_common::{context::LemmyContext, utils::sanitize_html_api_opt, SuccessResponse}; +use lemmy_api_common::{context::LemmyContext, SuccessResponse}; use lemmy_db_schema::{ newtypes::DbUrl, source::{ @@ -20,6 +20,7 @@ use lemmy_db_schema::{ post::{PostSaved, PostSavedForm}, }, traits::{Blockable, Crud, Followable, Saveable}, + utils::diesel_option_overwrite, }; use lemmy_db_views::structs::LocalUserView; use lemmy_utils::{ @@ -96,8 +97,8 @@ pub async fn import_settings( local_user_view: LocalUserView, context: Data, ) -> Result, LemmyError> { - let display_name = Some(sanitize_html_api_opt(&data.display_name)); - let bio = Some(sanitize_html_api_opt(&data.bio)); + let display_name = diesel_option_overwrite(data.display_name.clone()); + let bio = diesel_option_overwrite(data.bio.clone()); let person_form = PersonUpdateForm { display_name, diff --git a/crates/apub/src/objects/post.rs b/crates/apub/src/objects/post.rs index c25f988dc..4aa398bc2 100644 --- a/crates/apub/src/objects/post.rs +++ b/crates/apub/src/objects/post.rs @@ -21,7 +21,7 @@ use activitypub_federation::{ }; use anyhow::anyhow; use chrono::{DateTime, Utc}; -use html2md::parse_html; +use html2text::{from_read_with_decorator, render::text_renderer::TrivialDecorator}; use lemmy_api_common::{ context::LemmyContext, request::fetch_site_data, @@ -48,6 +48,7 @@ use lemmy_utils::{ }, }; use std::ops::Deref; +use stringreader::StringReader; use url::Url; const MAX_TITLE_LENGTH: usize = 200; @@ -171,11 +172,21 @@ impl Object for ApubPost { .name .clone() .or_else(|| { + // Posts coming from Mastodon or similar platforms don't have a title. Instead we take the + // first line of the content and convert it from HTML to plaintext. We also remove mentions + // of the community name. page .content - .clone() - .as_ref() - .and_then(|c| parse_html(c).lines().next().map(ToString::to_string)) + .as_deref() + .map(StringReader::new) + .map(|c| from_read_with_decorator(c, MAX_TITLE_LENGTH, TrivialDecorator::new())) + .and_then(|c| { + c.lines().next().map(|s| { + s.replace(&format!("@{}", community.name), "") + .trim() + .to_string() + }) + }) }) .ok_or_else(|| anyhow!("Object must have name or content"))?; if name.chars().count() > MAX_TITLE_LENGTH { @@ -288,8 +299,9 @@ mod tests { use super::*; use crate::{ objects::{ - community::tests::parse_lemmy_community, - person::tests::parse_lemmy_person, + community::{tests::parse_lemmy_community, ApubCommunity}, + instance::ApubSite, + person::{tests::parse_lemmy_person, ApubPerson}, post::ApubPost, tests::init_context, }, @@ -318,6 +330,31 @@ mod tests { assert!(!post.featured_community); assert_eq!(context.request_count(), 0); + cleanup(&context, person, site, community, post).await; + } + + #[tokio::test] + #[serial] + async fn test_convert_mastodon_post_title() { + let context = init_context().await; + let (person, site) = parse_lemmy_person(&context).await; + let community = parse_lemmy_community(&context).await; + + let json = file_to_json_object("assets/mastodon/objects/page.json").unwrap(); + let post = ApubPost::from_json(json, &context).await.unwrap(); + + assert_eq!(post.name, "Variable never resetting at refresh"); + + cleanup(&context, person, site, community, post).await; + } + + async fn cleanup( + context: &Data, + person: ApubPerson, + site: ApubSite, + community: ApubCommunity, + post: ApubPost, + ) { Post::delete(&mut context.pool(), post.id).await.unwrap(); Person::delete(&mut context.pool(), person.id) .await From 9e099726e6e36134032b21a568593d9227146a57 Mon Sep 17 00:00:00 2001 From: Nutomic Date: Fri, 13 Oct 2023 15:48:18 +0200 Subject: [PATCH 06/24] Cleanup checks for community actions (fixes #2858, fixes #2868) (#4028) * Cleanup checks for community actions (fixes #2858, fixes #2868) * allow restoring deleted community * review changes * remove unneeded sql * remove joins * change mod log check --- crates/api/src/comment/distinguish.rs | 13 ++- crates/api/src/comment/like.rs | 6 +- crates/api/src/comment_report/create.rs | 9 +- crates/api/src/comment_report/list.rs | 3 + crates/api/src/comment_report/resolve.rs | 10 +- crates/api/src/community/add_mod.rs | 10 +- crates/api/src/community/ban.rs | 9 +- crates/api/src/community/follow.rs | 6 +- crates/api/src/community/transfer.rs | 5 +- crates/api/src/local_user/login.rs | 6 +- crates/api/src/local_user/report_count.rs | 3 + crates/api/src/post/feature.rs | 29 +++-- crates/api/src/post/like.rs | 14 +-- crates/api/src/post/lock.rs | 27 ++--- crates/api/src/post_report/create.rs | 9 +- crates/api/src/post_report/list.rs | 3 + crates/api/src/post_report/resolve.rs | 10 +- crates/api/src/site/mod_log.rs | 29 ++--- crates/api_common/src/build_response.rs | 15 ++- crates/api_common/src/utils.rs | 103 +++++++++++++----- crates/api_crud/src/comment/create.rs | 6 +- crates/api_crud/src/comment/delete.rs | 6 +- crates/api_crud/src/comment/remove.rs | 15 +-- crates/api_crud/src/comment/update.rs | 6 +- crates/api_crud/src/community/delete.rs | 10 +- crates/api_crud/src/community/remove.rs | 10 +- crates/api_crud/src/community/update.rs | 19 ++-- crates/api_crud/src/post/create.rs | 10 +- crates/api_crud/src/post/delete.rs | 18 +-- crates/api_crud/src/post/remove.rs | 26 ++--- crates/api_crud/src/post/update.rs | 8 +- .../activities/create_or_update/comment.rs | 2 +- crates/apub/src/activities/mod.rs | 4 +- crates/apub/src/objects/post.rs | 2 +- crates/db_schema/src/impls/person.rs | 9 -- .../src/community_person_ban_view.rs | 22 ++-- crates/routes/src/lib.rs | 6 +- src/session_middleware.rs | 6 +- 38 files changed, 273 insertions(+), 231 deletions(-) diff --git a/crates/api/src/comment/distinguish.rs b/crates/api/src/comment/distinguish.rs index 5059e2db7..f29e01f76 100644 --- a/crates/api/src/comment/distinguish.rs +++ b/crates/api/src/comment/distinguish.rs @@ -2,7 +2,7 @@ use actix_web::web::{Data, Json}; use lemmy_api_common::{ comment::{CommentResponse, DistinguishComment}, context::LemmyContext, - utils::{check_community_ban, is_mod_or_admin}, + utils::{check_community_mod_action, check_community_user_action}, }; use lemmy_db_schema::{ source::comment::{Comment, CommentUpdateForm}, @@ -19,18 +19,19 @@ pub async fn distinguish_comment( ) -> Result, LemmyError> { let orig_comment = CommentView::read(&mut context.pool(), data.comment_id, None).await?; - check_community_ban( - local_user_view.person.id, + check_community_user_action( + &local_user_view.person, orig_comment.community.id, &mut context.pool(), ) .await?; // Verify that only a mod or admin can distinguish a comment - is_mod_or_admin( - &mut context.pool(), - local_user_view.person.id, + check_community_mod_action( + &local_user_view.person, orig_comment.community.id, + false, + &mut context.pool(), ) .await?; diff --git a/crates/api/src/comment/like.rs b/crates/api/src/comment/like.rs index ee0a0bd7e..e11a3e155 100644 --- a/crates/api/src/comment/like.rs +++ b/crates/api/src/comment/like.rs @@ -5,7 +5,7 @@ use lemmy_api_common::{ comment::{CommentResponse, CreateCommentLike}, context::LemmyContext, send_activity::{ActivityChannel, SendActivityData}, - utils::{check_community_ban, check_downvotes_enabled}, + utils::{check_community_user_action, check_downvotes_enabled}, }; use lemmy_db_schema::{ newtypes::LocalUserId, @@ -36,8 +36,8 @@ pub async fn like_comment( let comment_id = data.comment_id; let orig_comment = CommentView::read(&mut context.pool(), comment_id, None).await?; - check_community_ban( - local_user_view.person.id, + check_community_user_action( + &local_user_view.person, orig_comment.community.id, &mut context.pool(), ) diff --git a/crates/api/src/comment_report/create.rs b/crates/api/src/comment_report/create.rs index e1040fd5e..be892acfe 100644 --- a/crates/api/src/comment_report/create.rs +++ b/crates/api/src/comment_report/create.rs @@ -5,7 +5,7 @@ use lemmy_api_common::{ comment::{CommentReportResponse, CreateCommentReport}, context::LemmyContext, send_activity::{ActivityChannel, SendActivityData}, - utils::{check_community_ban, send_new_report_email_to_admins}, + utils::{check_community_user_action, send_new_report_email_to_admins}, }; use lemmy_db_schema::{ source::{ @@ -33,7 +33,12 @@ pub async fn create_comment_report( let comment_id = data.comment_id; let comment_view = CommentView::read(&mut context.pool(), comment_id, None).await?; - check_community_ban(person_id, comment_view.community.id, &mut context.pool()).await?; + check_community_user_action( + &local_user_view.person, + comment_view.community.id, + &mut context.pool(), + ) + .await?; let report_form = CommentReportForm { creator_id: person_id, diff --git a/crates/api/src/comment_report/list.rs b/crates/api/src/comment_report/list.rs index bc8dd2677..3d434deba 100644 --- a/crates/api/src/comment_report/list.rs +++ b/crates/api/src/comment_report/list.rs @@ -2,6 +2,7 @@ use actix_web::web::{Data, Json, Query}; use lemmy_api_common::{ comment::{ListCommentReports, ListCommentReportsResponse}, context::LemmyContext, + utils::check_community_mod_action_opt, }; use lemmy_db_views::{comment_report_view::CommentReportQuery, structs::LocalUserView}; use lemmy_utils::error::LemmyError; @@ -17,6 +18,8 @@ pub async fn list_comment_reports( let community_id = data.community_id; let unresolved_only = data.unresolved_only.unwrap_or_default(); + check_community_mod_action_opt(&local_user_view, community_id, &mut context.pool()).await?; + let page = data.page; let limit = data.limit; let comment_reports = CommentReportQuery { diff --git a/crates/api/src/comment_report/resolve.rs b/crates/api/src/comment_report/resolve.rs index 8296f068b..41ebe0d00 100644 --- a/crates/api/src/comment_report/resolve.rs +++ b/crates/api/src/comment_report/resolve.rs @@ -2,7 +2,7 @@ use actix_web::web::{Data, Json}; use lemmy_api_common::{ comment::{CommentReportResponse, ResolveCommentReport}, context::LemmyContext, - utils::is_mod_or_admin, + utils::check_community_mod_action, }; use lemmy_db_schema::{source::comment_report::CommentReport, traits::Reportable}; use lemmy_db_views::structs::{CommentReportView, LocalUserView}; @@ -20,7 +20,13 @@ pub async fn resolve_comment_report( let report = CommentReportView::read(&mut context.pool(), report_id, person_id).await?; let person_id = local_user_view.person.id; - is_mod_or_admin(&mut context.pool(), person_id, report.community.id).await?; + check_community_mod_action( + &local_user_view.person, + report.community.id, + false, + &mut context.pool(), + ) + .await?; if data.resolved { CommentReport::resolve(&mut context.pool(), report_id, person_id) diff --git a/crates/api/src/community/add_mod.rs b/crates/api/src/community/add_mod.rs index e08686361..9d055c654 100644 --- a/crates/api/src/community/add_mod.rs +++ b/crates/api/src/community/add_mod.rs @@ -4,7 +4,7 @@ use lemmy_api_common::{ community::{AddModToCommunity, AddModToCommunityResponse}, context::LemmyContext, send_activity::{ActivityChannel, SendActivityData}, - utils::is_mod_or_admin, + utils::check_community_mod_action, }; use lemmy_db_schema::{ source::{ @@ -26,7 +26,13 @@ pub async fn add_mod_to_community( let community_id = data.community_id; // Verify that only mods or admins can add mod - is_mod_or_admin(&mut context.pool(), local_user_view.person.id, community_id).await?; + check_community_mod_action( + &local_user_view.person, + community_id, + false, + &mut context.pool(), + ) + .await?; let community = Community::read(&mut context.pool(), community_id).await?; if local_user_view.local_user.admin && !community.local { Err(LemmyErrorType::NotAModerator)? diff --git a/crates/api/src/community/ban.rs b/crates/api/src/community/ban.rs index 2cc40cd1d..8e9aedbad 100644 --- a/crates/api/src/community/ban.rs +++ b/crates/api/src/community/ban.rs @@ -4,7 +4,7 @@ use lemmy_api_common::{ community::{BanFromCommunity, BanFromCommunityResponse}, context::LemmyContext, send_activity::{ActivityChannel, SendActivityData}, - utils::{is_mod_or_admin, remove_user_data_in_community}, + utils::{check_community_mod_action, remove_user_data_in_community}, }; use lemmy_db_schema::{ source::{ @@ -36,10 +36,11 @@ pub async fn ban_from_community( let expires = data.expires.map(naive_from_unix); // Verify that only mods or admins can ban - is_mod_or_admin( - &mut context.pool(), - local_user_view.person.id, + check_community_mod_action( + &local_user_view.person, data.community_id, + false, + &mut context.pool(), ) .await?; is_valid_body_field(&data.reason, false)?; diff --git a/crates/api/src/community/follow.rs b/crates/api/src/community/follow.rs index 91ffce714..497aa83cf 100644 --- a/crates/api/src/community/follow.rs +++ b/crates/api/src/community/follow.rs @@ -4,7 +4,7 @@ use lemmy_api_common::{ community::{CommunityResponse, FollowCommunity}, context::LemmyContext, send_activity::{ActivityChannel, SendActivityData}, - utils::{check_community_ban, check_community_deleted_or_removed}, + utils::check_community_user_action, }; use lemmy_db_schema::{ source::{ @@ -32,8 +32,8 @@ pub async fn follow_community( if data.follow { if community.local { - check_community_ban(local_user_view.person.id, community.id, &mut context.pool()).await?; - check_community_deleted_or_removed(community.id, &mut context.pool()).await?; + check_community_user_action(&local_user_view.person, community.id, &mut context.pool()) + .await?; CommunityFollower::follow(&mut context.pool(), &community_follower_form) .await diff --git a/crates/api/src/community/transfer.rs b/crates/api/src/community/transfer.rs index fd2e293f8..340bb6b63 100644 --- a/crates/api/src/community/transfer.rs +++ b/crates/api/src/community/transfer.rs @@ -3,7 +3,7 @@ use anyhow::Context; use lemmy_api_common::{ community::{GetCommunityResponse, TransferCommunity}, context::LemmyContext, - utils::{is_admin, is_top_mod}, + utils::{check_community_user_action, is_admin, is_top_mod}, }; use lemmy_db_schema::{ source::{ @@ -27,11 +27,12 @@ pub async fn transfer_community( context: Data, local_user_view: LocalUserView, ) -> Result, LemmyError> { - // Fetch the community mods let community_id = data.community_id; let mut community_mods = CommunityModeratorView::for_community(&mut context.pool(), community_id).await?; + check_community_user_action(&local_user_view.person, community_id, &mut context.pool()).await?; + // Make sure transferrer is either the top community mod, or an admin if !(is_top_mod(&local_user_view, &community_mods).is_ok() || is_admin(&local_user_view).is_ok()) { diff --git a/crates/api/src/local_user/login.rs b/crates/api/src/local_user/login.rs index 981b76a09..f57fd0a70 100644 --- a/crates/api/src/local_user/login.rs +++ b/crates/api/src/local_user/login.rs @@ -44,11 +44,7 @@ pub async fn login( if !valid { Err(LemmyErrorType::IncorrectLogin)? } - check_user_valid( - local_user_view.person.banned, - local_user_view.person.ban_expires, - local_user_view.person.deleted, - )?; + check_user_valid(&local_user_view.person)?; // Check if the user's email is verified if email verification is turned on // However, skip checking verification if the user is an admin diff --git a/crates/api/src/local_user/report_count.rs b/crates/api/src/local_user/report_count.rs index 3bfa1559b..666886432 100644 --- a/crates/api/src/local_user/report_count.rs +++ b/crates/api/src/local_user/report_count.rs @@ -2,6 +2,7 @@ use actix_web::web::{Data, Json}; use lemmy_api_common::{ context::LemmyContext, person::{GetReportCount, GetReportCountResponse}, + utils::check_community_mod_action_opt, }; use lemmy_db_views::structs::{ CommentReportView, @@ -21,6 +22,8 @@ pub async fn report_count( let admin = local_user_view.local_user.admin; let community_id = data.community_id; + check_community_mod_action_opt(&local_user_view, community_id, &mut context.pool()).await?; + let comment_reports = CommentReportView::get_report_count(&mut context.pool(), person_id, admin, community_id) .await?; diff --git a/crates/api/src/post/feature.rs b/crates/api/src/post/feature.rs index 8c2d2fced..8c4b4978f 100644 --- a/crates/api/src/post/feature.rs +++ b/crates/api/src/post/feature.rs @@ -5,7 +5,7 @@ use lemmy_api_common::{ context::LemmyContext, post::{FeaturePost, PostResponse}, send_activity::{ActivityChannel, SendActivityData}, - utils::{check_community_ban, check_community_deleted_or_removed, is_admin, is_mod_or_admin}, + utils::{check_community_mod_action, is_admin}, }; use lemmy_db_schema::{ source::{ @@ -27,23 +27,15 @@ pub async fn feature_post( let post_id = data.post_id; let orig_post = Post::read(&mut context.pool(), post_id).await?; - check_community_ban( - local_user_view.person.id, + check_community_mod_action( + &local_user_view.person, orig_post.community_id, + false, &mut context.pool(), ) .await?; - check_community_deleted_or_removed(orig_post.community_id, &mut context.pool()).await?; - if data.feature_type == PostFeatureType::Community { - // Verify that only the mods can feature in community - is_mod_or_admin( - &mut context.pool(), - local_user_view.person.id, - orig_post.community_id, - ) - .await?; - } else { + if data.feature_type == PostFeatureType::Local { is_admin(&local_user_view)?; } @@ -72,12 +64,17 @@ pub async fn feature_post( ModFeaturePost::create(&mut context.pool(), &form).await?; - let person_id = local_user_view.person.id; ActivityChannel::submit_activity( - SendActivityData::FeaturePost(post, local_user_view.person, data.featured), + SendActivityData::FeaturePost(post, local_user_view.person.clone(), data.featured), &context, ) .await?; - build_post_response(&context, orig_post.community_id, person_id, post_id).await + build_post_response( + &context, + orig_post.community_id, + &local_user_view.person, + post_id, + ) + .await } diff --git a/crates/api/src/post/like.rs b/crates/api/src/post/like.rs index d4f9d644d..751d1b9e5 100644 --- a/crates/api/src/post/like.rs +++ b/crates/api/src/post/like.rs @@ -5,12 +5,7 @@ use lemmy_api_common::{ context::LemmyContext, post::{CreatePostLike, PostResponse}, send_activity::{ActivityChannel, SendActivityData}, - utils::{ - check_community_ban, - check_community_deleted_or_removed, - check_downvotes_enabled, - mark_post_as_read, - }, + utils::{check_community_user_action, check_downvotes_enabled, mark_post_as_read}, }; use lemmy_db_schema::{ source::{ @@ -39,13 +34,12 @@ pub async fn like_post( let post_id = data.post_id; let post = Post::read(&mut context.pool(), post_id).await?; - check_community_ban( - local_user_view.person.id, + check_community_user_action( + &local_user_view.person, post.community_id, &mut context.pool(), ) .await?; - check_community_deleted_or_removed(post.community_id, &mut context.pool()).await?; let like_form = PostLikeForm { post_id: data.post_id, @@ -83,7 +77,7 @@ pub async fn like_post( build_post_response( context.deref(), post.community_id, - local_user_view.person.id, + &local_user_view.person, post_id, ) .await diff --git a/crates/api/src/post/lock.rs b/crates/api/src/post/lock.rs index ecd206156..b581f37a2 100644 --- a/crates/api/src/post/lock.rs +++ b/crates/api/src/post/lock.rs @@ -5,7 +5,7 @@ use lemmy_api_common::{ context::LemmyContext, post::{LockPost, PostResponse}, send_activity::{ActivityChannel, SendActivityData}, - utils::{check_community_ban, check_community_deleted_or_removed, is_mod_or_admin}, + utils::check_community_mod_action, }; use lemmy_db_schema::{ source::{ @@ -26,21 +26,13 @@ pub async fn lock_post( let post_id = data.post_id; let orig_post = Post::read(&mut context.pool(), post_id).await?; - check_community_ban( - local_user_view.person.id, + check_community_mod_action( + &local_user_view.person, orig_post.community_id, + false, &mut context.pool(), ) .await?; - check_community_deleted_or_removed(orig_post.community_id, &mut context.pool()).await?; - - // Verify that only the mods can lock - is_mod_or_admin( - &mut context.pool(), - local_user_view.person.id, - orig_post.community_id, - ) - .await?; // Update the post let post_id = data.post_id; @@ -63,12 +55,17 @@ pub async fn lock_post( }; ModLockPost::create(&mut context.pool(), &form).await?; - let person_id = local_user_view.person.id; ActivityChannel::submit_activity( - SendActivityData::LockPost(post, local_user_view.person, data.locked), + SendActivityData::LockPost(post, local_user_view.person.clone(), data.locked), &context, ) .await?; - build_post_response(&context, orig_post.community_id, person_id, post_id).await + build_post_response( + &context, + orig_post.community_id, + &local_user_view.person, + post_id, + ) + .await } diff --git a/crates/api/src/post_report/create.rs b/crates/api/src/post_report/create.rs index 126475c0c..e4ce2444a 100644 --- a/crates/api/src/post_report/create.rs +++ b/crates/api/src/post_report/create.rs @@ -5,7 +5,7 @@ use lemmy_api_common::{ context::LemmyContext, post::{CreatePostReport, PostReportResponse}, send_activity::{ActivityChannel, SendActivityData}, - utils::{check_community_ban, send_new_report_email_to_admins}, + utils::{check_community_user_action, send_new_report_email_to_admins}, }; use lemmy_db_schema::{ source::{ @@ -33,7 +33,12 @@ pub async fn create_post_report( let post_id = data.post_id; let post_view = PostView::read(&mut context.pool(), post_id, None, false).await?; - check_community_ban(person_id, post_view.community.id, &mut context.pool()).await?; + check_community_user_action( + &local_user_view.person, + post_view.community.id, + &mut context.pool(), + ) + .await?; let report_form = PostReportForm { creator_id: person_id, diff --git a/crates/api/src/post_report/list.rs b/crates/api/src/post_report/list.rs index 5cf2889ea..420052e35 100644 --- a/crates/api/src/post_report/list.rs +++ b/crates/api/src/post_report/list.rs @@ -2,6 +2,7 @@ use actix_web::web::{Data, Json, Query}; use lemmy_api_common::{ context::LemmyContext, post::{ListPostReports, ListPostReportsResponse}, + utils::check_community_mod_action_opt, }; use lemmy_db_views::{post_report_view::PostReportQuery, structs::LocalUserView}; use lemmy_utils::error::LemmyError; @@ -17,6 +18,8 @@ pub async fn list_post_reports( let community_id = data.community_id; let unresolved_only = data.unresolved_only.unwrap_or_default(); + check_community_mod_action_opt(&local_user_view, community_id, &mut context.pool()).await?; + let page = data.page; let limit = data.limit; let post_reports = PostReportQuery { diff --git a/crates/api/src/post_report/resolve.rs b/crates/api/src/post_report/resolve.rs index fe01c748c..3604055fd 100644 --- a/crates/api/src/post_report/resolve.rs +++ b/crates/api/src/post_report/resolve.rs @@ -2,7 +2,7 @@ use actix_web::web::{Data, Json}; use lemmy_api_common::{ context::LemmyContext, post::{PostReportResponse, ResolvePostReport}, - utils::is_mod_or_admin, + utils::check_community_mod_action, }; use lemmy_db_schema::{source::post_report::PostReport, traits::Reportable}; use lemmy_db_views::structs::{LocalUserView, PostReportView}; @@ -20,7 +20,13 @@ pub async fn resolve_post_report( let report = PostReportView::read(&mut context.pool(), report_id, person_id).await?; let person_id = local_user_view.person.id; - is_mod_or_admin(&mut context.pool(), person_id, report.community.id).await?; + check_community_mod_action( + &local_user_view.person, + report.community.id, + false, + &mut context.pool(), + ) + .await?; if data.resolved { PostReport::resolve(&mut context.pool(), report_id, person_id) diff --git a/crates/api/src/site/mod_log.rs b/crates/api/src/site/mod_log.rs index 9c66c72a1..133cce8d8 100644 --- a/crates/api/src/site/mod_log.rs +++ b/crates/api/src/site/mod_log.rs @@ -2,13 +2,9 @@ use actix_web::web::{Data, Json, Query}; use lemmy_api_common::{ context::LemmyContext, site::{GetModlog, GetModlogResponse}, - utils::{check_private_instance, is_admin, is_mod_or_admin}, -}; -use lemmy_db_schema::{ - newtypes::{CommunityId, PersonId}, - source::local_site::LocalSite, - ModlogActionType, + utils::{check_community_mod_action_opt, check_private_instance, is_admin}, }; +use lemmy_db_schema::{source::local_site::LocalSite, ModlogActionType}; use lemmy_db_views::structs::LocalUserView; use lemmy_db_views_moderator::structs::{ AdminPurgeCommentView, @@ -44,19 +40,16 @@ pub async fn get_mod_log( let type_ = data.type_.unwrap_or(All); let community_id = data.community_id; - let (local_person_id, is_admin) = match local_user_view { - Some(s) => (s.person.id, is_admin(&s).is_ok()), - None => (PersonId(-1), false), + let is_mod_or_admin = if let Some(local_user_view) = local_user_view { + let is_mod = community_id.is_some() + && check_community_mod_action_opt(&local_user_view, community_id, &mut context.pool()) + .await + .is_ok(); + is_mod || is_admin(&local_user_view).is_ok() + } else { + false }; - let community_id_value = match community_id { - Some(s) => s, - None => CommunityId(-1), - }; - let is_mod_of_community = data.community_id.is_some() - && is_mod_or_admin(&mut context.pool(), local_person_id, community_id_value) - .await - .is_ok(); - let hide_modlog_names = local_site.hide_modlog_mod_names && !is_mod_of_community && !is_admin; + let hide_modlog_names = local_site.hide_modlog_mod_names && !is_mod_or_admin; let mod_person_id = if hide_modlog_names { None diff --git a/crates/api_common/src/build_response.rs b/crates/api_common/src/build_response.rs index 5083616c1..a85e29765 100644 --- a/crates/api_common/src/build_response.rs +++ b/crates/api_common/src/build_response.rs @@ -7,7 +7,7 @@ use crate::{ }; use actix_web::web::Json; use lemmy_db_schema::{ - newtypes::{CommentId, CommunityId, LocalUserId, PersonId, PostId}, + newtypes::{CommentId, CommunityId, LocalUserId, PostId}, source::{ actor_language::CommunityLanguage, comment::Comment, @@ -44,10 +44,9 @@ pub async fn build_community_response( local_user_view: LocalUserView, community_id: CommunityId, ) -> Result, LemmyError> { - let is_mod_or_admin = - is_mod_or_admin(&mut context.pool(), local_user_view.person.id, community_id) - .await - .is_ok(); + let is_mod_or_admin = is_mod_or_admin(&mut context.pool(), &local_user_view.person, community_id) + .await + .is_ok(); let person_id = local_user_view.person.id; let community_view = CommunityView::read( &mut context.pool(), @@ -67,16 +66,16 @@ pub async fn build_community_response( pub async fn build_post_response( context: &LemmyContext, community_id: CommunityId, - person_id: PersonId, + person: &Person, post_id: PostId, ) -> Result, LemmyError> { - let is_mod_or_admin = is_mod_or_admin(&mut context.pool(), person_id, community_id) + let is_mod_or_admin = is_mod_or_admin(&mut context.pool(), person, community_id) .await .is_ok(); let post_view = PostView::read( &mut context.pool(), post_id, - Some(person_id), + Some(person.id), is_mod_or_admin, ) .await?; diff --git a/crates/api_common/src/utils.rs b/crates/api_common/src/utils.rs index 4dfad3645..139620b67 100644 --- a/crates/api_common/src/utils.rs +++ b/crates/api_common/src/utils.rs @@ -6,9 +6,7 @@ use crate::{ }; use actix_web::cookie::{Cookie, SameSite}; use anyhow::Context; -use chrono::{DateTime, Utc}; use lemmy_db_schema::{ - impls::person::is_banned, newtypes::{CommunityId, DbUrl, PersonId, PostId}, source::{ comment::{Comment, CommentUpdateForm}, @@ -33,7 +31,7 @@ use lemmy_db_views_actor::structs::{ }; use lemmy_utils::{ email::{send_email, translations::Lang}, - error::{LemmyError, LemmyErrorExt, LemmyErrorType}, + error::{LemmyError, LemmyErrorExt, LemmyErrorType, LemmyResult}, location_info, rate_limit::RateLimitConfig, settings::structs::Settings, @@ -49,10 +47,12 @@ pub static AUTH_COOKIE_NAME: &str = "auth"; #[tracing::instrument(skip_all)] pub async fn is_mod_or_admin( pool: &mut DbPool<'_>, - person_id: PersonId, + person: &Person, community_id: CommunityId, ) -> Result<(), LemmyError> { - let is_mod_or_admin = CommunityView::is_mod_or_admin(pool, person_id, community_id).await?; + check_user_valid(person)?; + + let is_mod_or_admin = CommunityView::is_mod_or_admin(pool, person.id, community_id).await?; if !is_mod_or_admin { Err(LemmyErrorType::NotAModOrAdmin)? } else { @@ -68,7 +68,7 @@ pub async fn is_mod_or_admin_opt( ) -> Result<(), LemmyError> { if let Some(local_user_view) = local_user_view { if let Some(community_id) = community_id { - is_mod_or_admin(pool, local_user_view.person.id, community_id).await + is_mod_or_admin(pool, &local_user_view.person, community_id).await } else { is_admin(local_user_view) } @@ -78,8 +78,11 @@ pub async fn is_mod_or_admin_opt( } pub fn is_admin(local_user_view: &LocalUserView) -> Result<(), LemmyError> { + check_user_valid(&local_user_view.person)?; if !local_user_view.local_user.admin { Err(LemmyErrorType::NotAnAdmin)? + } else if local_user_view.person.banned { + Err(LemmyErrorType::Banned)? } else { Ok(()) } @@ -89,6 +92,7 @@ pub fn is_top_mod( local_user_view: &LocalUserView, community_mods: &[CommunityModeratorView], ) -> Result<(), LemmyError> { + check_user_valid(&local_user_view.person)?; if local_user_view.person.id != community_mods .first() @@ -134,52 +138,91 @@ pub async fn mark_post_as_unread( .with_lemmy_type(LemmyErrorType::CouldntMarkPostAsRead) } -pub fn check_user_valid( - banned: bool, - ban_expires: Option>, - deleted: bool, -) -> Result<(), LemmyError> { +pub fn check_user_valid(person: &Person) -> Result<(), LemmyError> { // Check for a site ban - if is_banned(banned, ban_expires) { + if person.banned { Err(LemmyErrorType::SiteBan)? } // check for account deletion - else if deleted { + else if person.deleted { Err(LemmyErrorType::Deleted)? } else { Ok(()) } } -#[tracing::instrument(skip_all)] -pub async fn check_community_ban( - person_id: PersonId, +/// Checks that a normal user action (eg posting or voting) is allowed in a given community. +/// +/// In particular it checks that neither the user nor community are banned or deleted, and that +/// the user isn't banned. +pub async fn check_community_user_action( + person: &Person, community_id: CommunityId, pool: &mut DbPool<'_>, -) -> Result<(), LemmyError> { - let is_banned = CommunityPersonBanView::get(pool, person_id, community_id) - .await - .is_ok(); - if is_banned { - Err(LemmyErrorType::BannedFromCommunity)? - } else { - Ok(()) - } +) -> LemmyResult<()> { + check_user_valid(person)?; + check_community_deleted_removed(community_id, pool).await?; + check_community_ban(person, community_id, pool).await?; + Ok(()) } -#[tracing::instrument(skip_all)] -pub async fn check_community_deleted_or_removed( +async fn check_community_deleted_removed( community_id: CommunityId, pool: &mut DbPool<'_>, -) -> Result<(), LemmyError> { +) -> LemmyResult<()> { let community = Community::read(pool, community_id) .await .with_lemmy_type(LemmyErrorType::CouldntFindCommunity)?; if community.deleted || community.removed { Err(LemmyErrorType::Deleted)? - } else { - Ok(()) } + Ok(()) +} + +async fn check_community_ban( + person: &Person, + community_id: CommunityId, + pool: &mut DbPool<'_>, +) -> LemmyResult<()> { + // check if user was banned from site or community + let is_banned = CommunityPersonBanView::get(pool, person.id, community_id).await?; + if is_banned { + Err(LemmyErrorType::BannedFromCommunity)? + } + Ok(()) +} + +/// Check that the given user can perform a mod action in the community. +/// +/// In particular it checks that he is an admin or mod, wasn't banned and the community isn't +/// removed/deleted. +pub async fn check_community_mod_action( + person: &Person, + community_id: CommunityId, + allow_deleted: bool, + pool: &mut DbPool<'_>, +) -> LemmyResult<()> { + is_mod_or_admin(pool, person, community_id).await?; + check_community_ban(person, community_id, pool).await?; + + // it must be possible to restore deleted community + if !allow_deleted { + check_community_deleted_removed(community_id, pool).await?; + } + Ok(()) +} + +pub async fn check_community_mod_action_opt( + local_user_view: &LocalUserView, + community_id: Option, + pool: &mut DbPool<'_>, +) -> LemmyResult<()> { + if let Some(community_id) = community_id { + check_community_mod_action(&local_user_view.person, community_id, false, pool).await?; + } else { + is_admin(local_user_view)?; + } + Ok(()) } pub fn check_post_deleted_or_removed(post: &Post) -> Result<(), LemmyError> { diff --git a/crates/api_crud/src/comment/create.rs b/crates/api_crud/src/comment/create.rs index dcf998bd7..2e719eda2 100644 --- a/crates/api_crud/src/comment/create.rs +++ b/crates/api_crud/src/comment/create.rs @@ -6,8 +6,7 @@ use lemmy_api_common::{ context::LemmyContext, send_activity::{ActivityChannel, SendActivityData}, utils::{ - check_community_ban, - check_community_deleted_or_removed, + check_community_user_action, check_post_deleted_or_removed, generate_local_apub_endpoint, get_post, @@ -57,8 +56,7 @@ pub async fn create_comment( let post = get_post(post_id, &mut context.pool()).await?; let community_id = post.community_id; - check_community_ban(local_user_view.person.id, community_id, &mut context.pool()).await?; - check_community_deleted_or_removed(community_id, &mut context.pool()).await?; + check_community_user_action(&local_user_view.person, community_id, &mut context.pool()).await?; check_post_deleted_or_removed(&post)?; // Check if post is locked, no new comments diff --git a/crates/api_crud/src/comment/delete.rs b/crates/api_crud/src/comment/delete.rs index 1c986d03c..2de2a7955 100644 --- a/crates/api_crud/src/comment/delete.rs +++ b/crates/api_crud/src/comment/delete.rs @@ -5,7 +5,7 @@ use lemmy_api_common::{ comment::{CommentResponse, DeleteComment}, context::LemmyContext, send_activity::{ActivityChannel, SendActivityData}, - utils::check_community_ban, + utils::check_community_user_action, }; use lemmy_db_schema::{ source::{ @@ -31,8 +31,8 @@ pub async fn delete_comment( Err(LemmyErrorType::CouldntUpdateComment)? } - check_community_ban( - local_user_view.person.id, + check_community_user_action( + &local_user_view.person, orig_comment.community.id, &mut context.pool(), ) diff --git a/crates/api_crud/src/comment/remove.rs b/crates/api_crud/src/comment/remove.rs index 601101bb8..cbfbcd22c 100644 --- a/crates/api_crud/src/comment/remove.rs +++ b/crates/api_crud/src/comment/remove.rs @@ -5,7 +5,7 @@ use lemmy_api_common::{ comment::{CommentResponse, RemoveComment}, context::LemmyContext, send_activity::{ActivityChannel, SendActivityData}, - utils::{check_community_ban, is_mod_or_admin}, + utils::check_community_mod_action, }; use lemmy_db_schema::{ source::{ @@ -27,21 +27,14 @@ pub async fn remove_comment( let comment_id = data.comment_id; let orig_comment = CommentView::read(&mut context.pool(), comment_id, None).await?; - check_community_ban( - local_user_view.person.id, + check_community_mod_action( + &local_user_view.person, orig_comment.community.id, + false, &mut context.pool(), ) .await?; - // Verify that only a mod or admin can remove - is_mod_or_admin( - &mut context.pool(), - local_user_view.person.id, - orig_comment.community.id, - ) - .await?; - // Do the remove let removed = data.removed; let updated_comment = Comment::update( diff --git a/crates/api_crud/src/comment/update.rs b/crates/api_crud/src/comment/update.rs index d6c672262..21cf54cfa 100644 --- a/crates/api_crud/src/comment/update.rs +++ b/crates/api_crud/src/comment/update.rs @@ -5,7 +5,7 @@ use lemmy_api_common::{ comment::{CommentResponse, EditComment}, context::LemmyContext, send_activity::{ActivityChannel, SendActivityData}, - utils::{check_community_ban, local_site_to_slur_regex}, + utils::{check_community_user_action, local_site_to_slur_regex}, }; use lemmy_db_schema::{ source::{ @@ -37,8 +37,8 @@ pub async fn update_comment( let comment_id = data.comment_id; let orig_comment = CommentView::read(&mut context.pool(), comment_id, None).await?; - check_community_ban( - local_user_view.person.id, + check_community_user_action( + &local_user_view.person, orig_comment.community.id, &mut context.pool(), ) diff --git a/crates/api_crud/src/community/delete.rs b/crates/api_crud/src/community/delete.rs index 7c94e0ccc..60b79fd79 100644 --- a/crates/api_crud/src/community/delete.rs +++ b/crates/api_crud/src/community/delete.rs @@ -5,7 +5,7 @@ use lemmy_api_common::{ community::{CommunityResponse, DeleteCommunity}, context::LemmyContext, send_activity::{ActivityChannel, SendActivityData}, - utils::is_top_mod, + utils::{check_community_mod_action, is_top_mod}, }; use lemmy_db_schema::{ source::community::{Community, CommunityUpdateForm}, @@ -26,6 +26,14 @@ pub async fn delete_community( let community_mods = CommunityModeratorView::for_community(&mut context.pool(), community_id).await?; + check_community_mod_action( + &local_user_view.person, + community_id, + true, + &mut context.pool(), + ) + .await?; + // Make sure deleter is the top mod is_top_mod(&local_user_view, &community_mods)?; diff --git a/crates/api_crud/src/community/remove.rs b/crates/api_crud/src/community/remove.rs index 3a2cc654a..9604b0432 100644 --- a/crates/api_crud/src/community/remove.rs +++ b/crates/api_crud/src/community/remove.rs @@ -5,7 +5,7 @@ use lemmy_api_common::{ community::{CommunityResponse, RemoveCommunity}, context::LemmyContext, send_activity::{ActivityChannel, SendActivityData}, - utils::is_admin, + utils::{check_community_mod_action, is_admin}, }; use lemmy_db_schema::{ source::{ @@ -26,6 +26,14 @@ pub async fn remove_community( context: Data, local_user_view: LocalUserView, ) -> Result, LemmyError> { + check_community_mod_action( + &local_user_view.person, + data.community_id, + true, + &mut context.pool(), + ) + .await?; + // Verify its an admin (only an admin can remove a community) is_admin(&local_user_view)?; diff --git a/crates/api_crud/src/community/update.rs b/crates/api_crud/src/community/update.rs index 8e5ec77c0..40ba1a2a1 100644 --- a/crates/api_crud/src/community/update.rs +++ b/crates/api_crud/src/community/update.rs @@ -5,10 +5,9 @@ use lemmy_api_common::{ community::{CommunityResponse, EditCommunity}, context::LemmyContext, send_activity::{ActivityChannel, SendActivityData}, - utils::local_site_to_slur_regex, + utils::{check_community_mod_action, local_site_to_slur_regex}, }; use lemmy_db_schema::{ - newtypes::PersonId, source::{ actor_language::{CommunityLanguage, SiteLanguage}, community::{Community, CommunityUpdateForm}, @@ -18,7 +17,6 @@ use lemmy_db_schema::{ utils::{diesel_option_overwrite, diesel_option_overwrite_to_url, naive_now}, }; use lemmy_db_views::structs::LocalUserView; -use lemmy_db_views_actor::structs::CommunityModeratorView; use lemmy_utils::{ error::{LemmyError, LemmyErrorExt, LemmyErrorType}, utils::{slurs::check_slurs_opt, validation::is_valid_body_field}, @@ -42,14 +40,13 @@ pub async fn update_community( let description = diesel_option_overwrite(data.description.clone()); // Verify its a mod (only mods can edit it) - let community_id = data.community_id; - let mods: Vec = - CommunityModeratorView::for_community(&mut context.pool(), community_id) - .await - .map(|v| v.into_iter().map(|m| m.moderator.id).collect())?; - if !mods.contains(&local_user_view.person.id) { - Err(LemmyErrorType::NotAModerator)? - } + check_community_mod_action( + &local_user_view.person, + data.community_id, + false, + &mut context.pool(), + ) + .await?; let community_id = data.community_id; if let Some(languages) = data.discussion_languages.clone() { diff --git a/crates/api_crud/src/post/create.rs b/crates/api_crud/src/post/create.rs index 33408b774..e4af92916 100644 --- a/crates/api_crud/src/post/create.rs +++ b/crates/api_crud/src/post/create.rs @@ -7,8 +7,7 @@ use lemmy_api_common::{ request::fetch_site_data, send_activity::{ActivityChannel, SendActivityData}, utils::{ - check_community_ban, - check_community_deleted_or_removed, + check_community_user_action, generate_local_apub_endpoint, honeypot_check, local_site_to_slur_regex, @@ -60,13 +59,12 @@ pub async fn create_post( is_valid_body_field(&data.body, true)?; check_url_scheme(&data.url)?; - check_community_ban( - local_user_view.person.id, + check_community_user_action( + &local_user_view.person, data.community_id, &mut context.pool(), ) .await?; - check_community_deleted_or_removed(data.community_id, &mut context.pool()).await?; let community_id = data.community_id; let community = Community::read(&mut context.pool(), community_id).await?; @@ -184,5 +182,5 @@ pub async fn create_post( }); }; - build_post_response(&context, community_id, person_id, post_id).await + build_post_response(&context, community_id, &local_user_view.person, post_id).await } diff --git a/crates/api_crud/src/post/delete.rs b/crates/api_crud/src/post/delete.rs index 90c95c6b2..630bfa357 100644 --- a/crates/api_crud/src/post/delete.rs +++ b/crates/api_crud/src/post/delete.rs @@ -5,7 +5,7 @@ use lemmy_api_common::{ context::LemmyContext, post::{DeletePost, PostResponse}, send_activity::{ActivityChannel, SendActivityData}, - utils::{check_community_ban, check_community_deleted_or_removed}, + utils::check_community_user_action, }; use lemmy_db_schema::{ source::post::{Post, PostUpdateForm}, @@ -28,13 +28,12 @@ pub async fn delete_post( Err(LemmyErrorType::CouldntUpdatePost)? } - check_community_ban( - local_user_view.person.id, + check_community_user_action( + &local_user_view.person, orig_post.community_id, &mut context.pool(), ) .await?; - check_community_deleted_or_removed(orig_post.community_id, &mut context.pool()).await?; // Verify that only the creator can delete if !Post::is_post_creator(local_user_view.person.id, orig_post.creator_id) { @@ -52,12 +51,17 @@ pub async fn delete_post( ) .await?; - let person_id = local_user_view.person.id; ActivityChannel::submit_activity( - SendActivityData::DeletePost(post, local_user_view.person, data.0.clone()), + SendActivityData::DeletePost(post, local_user_view.person.clone(), data.0.clone()), &context, ) .await?; - build_post_response(&context, orig_post.community_id, person_id, data.post_id).await + build_post_response( + &context, + orig_post.community_id, + &local_user_view.person, + data.post_id, + ) + .await } diff --git a/crates/api_crud/src/post/remove.rs b/crates/api_crud/src/post/remove.rs index 52f380d88..2dd35d598 100644 --- a/crates/api_crud/src/post/remove.rs +++ b/crates/api_crud/src/post/remove.rs @@ -5,7 +5,7 @@ use lemmy_api_common::{ context::LemmyContext, post::{PostResponse, RemovePost}, send_activity::{ActivityChannel, SendActivityData}, - utils::{check_community_ban, is_mod_or_admin}, + utils::check_community_mod_action, }; use lemmy_db_schema::{ source::{ @@ -26,21 +26,14 @@ pub async fn remove_post( let post_id = data.post_id; let orig_post = Post::read(&mut context.pool(), post_id).await?; - check_community_ban( - local_user_view.person.id, + check_community_mod_action( + &local_user_view.person, orig_post.community_id, + false, &mut context.pool(), ) .await?; - // Verify that only the mods can remove - is_mod_or_admin( - &mut context.pool(), - local_user_view.person.id, - orig_post.community_id, - ) - .await?; - // Update the post let post_id = data.post_id; let removed = data.removed; @@ -63,12 +56,17 @@ pub async fn remove_post( }; ModRemovePost::create(&mut context.pool(), &form).await?; - let person_id = local_user_view.person.id; ActivityChannel::submit_activity( - SendActivityData::RemovePost(post, local_user_view.person, data.0), + SendActivityData::RemovePost(post, local_user_view.person.clone(), data.0), &context, ) .await?; - build_post_response(&context, orig_post.community_id, person_id, post_id).await + build_post_response( + &context, + orig_post.community_id, + &local_user_view.person, + post_id, + ) + .await } diff --git a/crates/api_crud/src/post/update.rs b/crates/api_crud/src/post/update.rs index dfd311f3f..b17981c55 100644 --- a/crates/api_crud/src/post/update.rs +++ b/crates/api_crud/src/post/update.rs @@ -6,7 +6,7 @@ use lemmy_api_common::{ post::{EditPost, PostResponse}, request::fetch_site_data, send_activity::{ActivityChannel, SendActivityData}, - utils::{check_community_ban, local_site_to_slur_regex}, + utils::{check_community_user_action, local_site_to_slur_regex}, }; use lemmy_db_schema::{ source::{ @@ -55,8 +55,8 @@ pub async fn update_post( let post_id = data.post_id; let orig_post = Post::read(&mut context.pool(), post_id).await?; - check_community_ban( - local_user_view.person.id, + check_community_user_action( + &local_user_view.person, orig_post.community_id, &mut context.pool(), ) @@ -107,7 +107,7 @@ pub async fn update_post( build_post_response( context.deref(), orig_post.community_id, - local_user_view.person.id, + &local_user_view.person, post_id, ) .await diff --git a/crates/apub/src/activities/create_or_update/comment.rs b/crates/apub/src/activities/create_or_update/comment.rs index 502bc5e0c..e162709ba 100644 --- a/crates/apub/src/activities/create_or_update/comment.rs +++ b/crates/apub/src/activities/create_or_update/comment.rs @@ -140,7 +140,7 @@ impl ActivityHandler for CreateOrUpdateNote { if distinguished != existing_comment.distinguished { let creator = self.actor.dereference(context).await?; let (post, _) = self.object.get_parents(context).await?; - is_mod_or_admin(&mut context.pool(), creator.id, post.community_id).await?; + is_mod_or_admin(&mut context.pool(), &creator, post.community_id).await?; } } diff --git a/crates/apub/src/activities/mod.rs b/crates/apub/src/activities/mod.rs index 1cb266c21..936af8ad7 100644 --- a/crates/apub/src/activities/mod.rs +++ b/crates/apub/src/activities/mod.rs @@ -92,9 +92,7 @@ pub(crate) async fn verify_person_in_community( } let person_id = person.id; let community_id = community.id; - let is_banned = CommunityPersonBanView::get(&mut context.pool(), person_id, community_id) - .await - .is_ok(); + let is_banned = CommunityPersonBanView::get(&mut context.pool(), person_id, community_id).await?; if is_banned { Err(LemmyErrorType::PersonIsBannedFromCommunity)? } else { diff --git a/crates/apub/src/objects/post.rs b/crates/apub/src/objects/post.rs index 4aa398bc2..6aba17554 100644 --- a/crates/apub/src/objects/post.rs +++ b/crates/apub/src/objects/post.rs @@ -166,7 +166,7 @@ impl Object for ApubPost { let creator = page.creator()?.dereference(context).await?; let community = page.community(context).await?; if community.posting_restricted_to_mods { - is_mod_or_admin(&mut context.pool(), creator.id, community.id).await?; + is_mod_or_admin(&mut context.pool(), &creator, community.id).await?; } let mut name = page .name diff --git a/crates/db_schema/src/impls/person.rs b/crates/db_schema/src/impls/person.rs index 1a2974439..12ec0392f 100644 --- a/crates/db_schema/src/impls/person.rs +++ b/crates/db_schema/src/impls/person.rs @@ -11,7 +11,6 @@ use crate::{ traits::{ApubActor, Crud, Followable}, utils::{functions::lower, get_conn, naive_now, DbPool}, }; -use chrono::{DateTime, Utc}; use diesel::{dsl::insert_into, result::Error, ExpressionMethods, JoinOnDsl, QueryDsl}; use diesel_async::RunQueryDsl; @@ -87,14 +86,6 @@ impl Person { } } -pub fn is_banned(banned_: bool, expires: Option>) -> bool { - if let Some(expires) = expires { - banned_ && expires.gt(&naive_now()) - } else { - banned_ - } -} - #[async_trait] impl ApubActor for Person { async fn read_from_apub_id( diff --git a/crates/db_views_actor/src/community_person_ban_view.rs b/crates/db_views_actor/src/community_person_ban_view.rs index ba0b4cd7f..712bb2d3a 100644 --- a/crates/db_views_actor/src/community_person_ban_view.rs +++ b/crates/db_views_actor/src/community_person_ban_view.rs @@ -1,9 +1,9 @@ use crate::structs::CommunityPersonBanView; -use diesel::{result::Error, ExpressionMethods, QueryDsl}; +use diesel::{dsl::exists, result::Error, select, ExpressionMethods, QueryDsl}; use diesel_async::RunQueryDsl; use lemmy_db_schema::{ newtypes::{CommunityId, PersonId}, - schema::{community, community_person_ban, person}, + schema::community_person_ban, utils::{get_conn, DbPool}, }; @@ -12,16 +12,14 @@ impl CommunityPersonBanView { pool: &mut DbPool<'_>, from_person_id: PersonId, from_community_id: CommunityId, - ) -> Result { + ) -> Result { let conn = &mut get_conn(pool).await?; - community_person_ban::table - .inner_join(community::table) - .inner_join(person::table) - .select((community::all_columns, person::all_columns)) - .filter(community_person_ban::community_id.eq(from_community_id)) - .filter(community_person_ban::person_id.eq(from_person_id)) - .order_by(community_person_ban::published) - .first::(conn) - .await + select(exists( + community_person_ban::table + .filter(community_person_ban::community_id.eq(from_community_id)) + .filter(community_person_ban::person_id.eq(from_person_id)), + )) + .get_result::(conn) + .await } } diff --git a/crates/routes/src/lib.rs b/crates/routes/src/lib.rs index 28da113ef..ec28fda45 100644 --- a/crates/routes/src/lib.rs +++ b/crates/routes/src/lib.rs @@ -14,11 +14,7 @@ async fn local_user_view_from_jwt( ) -> Result { let local_user_id = Claims::validate(jwt, context).await?; let local_user_view = LocalUserView::read(&mut context.pool(), local_user_id).await?; - check_user_valid( - local_user_view.person.banned, - local_user_view.person.ban_expires, - local_user_view.person.deleted, - )?; + check_user_valid(&local_user_view.person)?; Ok(local_user_view) } diff --git a/src/session_middleware.rs b/src/session_middleware.rs index 1d19cec9c..80b79f917 100644 --- a/src/session_middleware.rs +++ b/src/session_middleware.rs @@ -109,11 +109,7 @@ async fn local_user_view_from_jwt( .await .with_lemmy_type(LemmyErrorType::NotLoggedIn)?; let local_user_view = LocalUserView::read(&mut context.pool(), local_user_id).await?; - check_user_valid( - local_user_view.person.banned, - local_user_view.person.ban_expires, - local_user_view.person.deleted, - )?; + check_user_valid(&local_user_view.person)?; Ok(local_user_view) } From 3be56ef2e0d6e6bf665fccf07b65db08fb10b5bd Mon Sep 17 00:00:00 2001 From: Dessalines Date: Mon, 16 Oct 2023 06:01:59 -0400 Subject: [PATCH 07/24] Trying to fix export ci, try 1. (#4038) --- crates/apub/src/api/user_settings_backup.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/apub/src/api/user_settings_backup.rs b/crates/apub/src/api/user_settings_backup.rs index c1102791d..aa09e51dc 100644 --- a/crates/apub/src/api/user_settings_backup.rs +++ b/crates/apub/src/api/user_settings_backup.rs @@ -377,13 +377,14 @@ mod tests { import_settings(backup, import_user.clone(), context.reset_request_count()) .await .unwrap(); - let import_user_updated = LocalUserView::read(&mut context.pool(), import_user.local_user.id) - .await - .unwrap(); // wait for background task to finish sleep(Duration::from_millis(100)).await; + let import_user_updated = LocalUserView::read(&mut context.pool(), import_user.local_user.id) + .await + .unwrap(); + assert_eq!( export_user.person.display_name, import_user_updated.person.display_name From 256ee619084dc51d8a23a01a0c4cb55b5be1279b Mon Sep 17 00:00:00 2001 From: Nutomic Date: Mon, 16 Oct 2023 12:03:49 +0200 Subject: [PATCH 08/24] Ignore activities in remote communities without local followers (#4006) * Ignore activities in remote communities without local followers (fixes #3568) * x * comments * prettier * fix api test * fix test * cleanup * fix remaining test * clippy * decrease delay --- api_tests/src/comment.spec.ts | 8 + api_tests/src/community.spec.ts | 57 +- api_tests/src/shared.ts | 14 +- api_tests/yarn.lock | 1572 +++++++++-------- .../apub/src/activities/community/announce.rs | 50 +- crates/apub/src/api/user_settings_backup.rs | 8 +- crates/db_schema/src/impls/community.rs | 16 + crates/utils/src/error.rs | 1 + 8 files changed, 917 insertions(+), 809 deletions(-) diff --git a/api_tests/src/comment.spec.ts b/api_tests/src/comment.spec.ts index 871545971..ed90621a7 100644 --- a/api_tests/src/comment.spec.ts +++ b/api_tests/src/comment.spec.ts @@ -35,6 +35,7 @@ import { delay, waitForPost, alphaUrl, + followCommunity, } from "./shared"; import { CommentView } from "lemmy-js-client/dist/types/CommentView"; import { CommunityView } from "lemmy-js-client"; @@ -500,6 +501,13 @@ test("A and G subscribe to B (center) A posts, G mentions B, it gets announced t throw "Missing alpha community"; } + // follow community from beta so that it accepts the mention + let betaCommunity = await resolveCommunity( + beta, + alphaCommunity.community.actor_id, + ); + await followCommunity(beta, true, betaCommunity.community!.community.id); + let alphaPost = await createPost(alpha, alphaCommunity.community.id); expect(alphaPost.post_view.community.local).toBe(true); diff --git a/api_tests/src/community.spec.ts b/api_tests/src/community.spec.ts index 18b56c152..f4d06d588 100644 --- a/api_tests/src/community.spec.ts +++ b/api_tests/src/community.spec.ts @@ -28,8 +28,10 @@ import { delay, waitForPost, alphaUrl, + betaAllowedInstances, + searchPostLocal, } from "./shared"; -import { LemmyHttp } from "lemmy-js-client"; +import { EditSite, LemmyHttp } from "lemmy-js-client"; beforeAll(async () => { await setupLogins(); @@ -376,3 +378,56 @@ test("User blocks instance, communities are hidden", async () => { let listing_ids3 = listing3.posts.map(p => p.post.ap_id); expect(listing_ids3).toContain(postRes.post_view.post.ap_id); }); + +test("Dont receive community activities after unsubscribe", async () => { + let communityRes = await createCommunity(alpha); + expect(communityRes.community_view.community.name).toBeDefined(); + expect(communityRes.community_view.counts.subscribers).toBe(1); + + let betaCommunity = ( + await resolveCommunity(beta, communityRes.community_view.community.actor_id) + ).community; + assertCommunityFederation(betaCommunity, communityRes.community_view); + + // follow alpha community from beta + await followCommunity(beta, true, betaCommunity!.community.id); + + // ensure that follower count was updated + let communityRes1 = await getCommunity( + alpha, + communityRes.community_view.community.id, + ); + expect(communityRes1.community_view.counts.subscribers).toBe(2); + + // temporarily block alpha, so that it doesnt know about unfollow + let editSiteForm: EditSite = {}; + editSiteForm.allowed_instances = ["lemmy-epsilon"]; + await beta.editSite(editSiteForm); + await delay(2000); + + // unfollow + await followCommunity(beta, false, betaCommunity!.community.id); + + // ensure that alpha still sees beta as follower + let communityRes2 = await getCommunity( + alpha, + communityRes.community_view.community.id, + ); + expect(communityRes2.community_view.counts.subscribers).toBe(2); + + // unblock alpha + editSiteForm.allowed_instances = betaAllowedInstances; + await beta.editSite(editSiteForm); + await delay(2000); + + // create a post, it shouldnt reach beta + let postRes = await createPost( + alpha, + communityRes.community_view.community.id, + ); + expect(postRes.post_view.post.id).toBeDefined(); + await delay(2000); + + let postResBeta = searchPostLocal(beta, postRes.post_view.post); + expect((await postResBeta).posts.length).toBe(0); +}); diff --git a/api_tests/src/shared.ts b/api_tests/src/shared.ts index 51431f084..e4dabb1d4 100644 --- a/api_tests/src/shared.ts +++ b/api_tests/src/shared.ts @@ -84,6 +84,13 @@ export let gamma = new LemmyHttp(gammaUrl); export let delta = new LemmyHttp(deltaUrl); export let epsilon = new LemmyHttp(epsilonUrl); +export let betaAllowedInstances = [ + "lemmy-alpha", + "lemmy-gamma", + "lemmy-delta", + "lemmy-epsilon", +]; + const password = "lemmylemmy"; export async function setupLogins() { @@ -150,12 +157,7 @@ export async function setupLogins() { ]; await alpha.editSite(editSiteForm); - editSiteForm.allowed_instances = [ - "lemmy-alpha", - "lemmy-gamma", - "lemmy-delta", - "lemmy-epsilon", - ]; + editSiteForm.allowed_instances = betaAllowedInstances; await beta.editSite(editSiteForm); editSiteForm.allowed_instances = [ diff --git a/api_tests/yarn.lock b/api_tests/yarn.lock index eb9859b8d..629f36216 100644 --- a/api_tests/yarn.lock +++ b/api_tests/yarn.lock @@ -15,157 +15,155 @@ "@jridgewell/gen-mapping" "^0.3.0" "@jridgewell/trace-mapping" "^0.3.9" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.18.6", "@babel/code-frame@^7.21.4": - version "7.21.4" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.21.4.tgz#d0fa9e4413aca81f2b23b9442797bda1826edb39" - integrity sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g== +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.22.13": + version "7.22.13" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.13.tgz#e3c1c099402598483b7a8c46a721d1038803755e" + integrity sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w== dependencies: - "@babel/highlight" "^7.18.6" + "@babel/highlight" "^7.22.13" + chalk "^2.4.2" -"@babel/compat-data@^7.21.5": - version "7.21.7" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.21.7.tgz#61caffb60776e49a57ba61a88f02bedd8714f6bc" - integrity sha512-KYMqFYTaenzMK4yUtf4EW9wc4N9ef80FsbMtkwool5zpwl4YrT1SdWYSTRcT94KO4hannogdS+LxY7L+arP3gA== +"@babel/compat-data@^7.22.9": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.22.20.tgz#8df6e96661209623f1975d66c35ffca66f3306d0" + integrity sha512-BQYjKbpXjoXwFW5jGqiizJQQT/aC7pFm9Ok1OWssonuguICi264lbgMzRp2ZMmRSlfkX6DsWDDcsrctK8Rwfiw== "@babel/core@^7.11.6", "@babel/core@^7.12.3": - version "7.21.8" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.21.8.tgz#2a8c7f0f53d60100ba4c32470ba0281c92aa9aa4" - integrity sha512-YeM22Sondbo523Sz0+CirSPnbj9bG3P0CdHcBZdqUuaeOaYEFbOLoGU7lebvGP6P5J/WE9wOn7u7C4J9HvS1xQ== + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.23.0.tgz#f8259ae0e52a123eb40f552551e647b506a94d83" + integrity sha512-97z/ju/Jy1rZmDxybphrBuI+jtJjFVoz7Mr9yUQVVVi+DNZE333uFQeMOqcCIy1x3WYBIbWftUSLmbNXNT7qFQ== dependencies: "@ampproject/remapping" "^2.2.0" - "@babel/code-frame" "^7.21.4" - "@babel/generator" "^7.21.5" - "@babel/helper-compilation-targets" "^7.21.5" - "@babel/helper-module-transforms" "^7.21.5" - "@babel/helpers" "^7.21.5" - "@babel/parser" "^7.21.8" - "@babel/template" "^7.20.7" - "@babel/traverse" "^7.21.5" - "@babel/types" "^7.21.5" - convert-source-map "^1.7.0" + "@babel/code-frame" "^7.22.13" + "@babel/generator" "^7.23.0" + "@babel/helper-compilation-targets" "^7.22.15" + "@babel/helper-module-transforms" "^7.23.0" + "@babel/helpers" "^7.23.0" + "@babel/parser" "^7.23.0" + "@babel/template" "^7.22.15" + "@babel/traverse" "^7.23.0" + "@babel/types" "^7.23.0" + convert-source-map "^2.0.0" debug "^4.1.0" gensync "^1.0.0-beta.2" - json5 "^2.2.2" - semver "^6.3.0" + json5 "^2.2.3" + semver "^6.3.1" -"@babel/generator@^7.21.5", "@babel/generator@^7.7.2": - version "7.21.5" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.21.5.tgz#c0c0e5449504c7b7de8236d99338c3e2a340745f" - integrity sha512-SrKK/sRv8GesIW1bDagf9cCG38IOMYZusoe1dfg0D8aiUe3Amvoj1QtjTPAWcfrZFvIwlleLb0gxzQidL9w14w== +"@babel/generator@^7.23.0", "@babel/generator@^7.7.2": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.0.tgz#df5c386e2218be505b34837acbcb874d7a983420" + integrity sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g== dependencies: - "@babel/types" "^7.21.5" + "@babel/types" "^7.23.0" "@jridgewell/gen-mapping" "^0.3.2" "@jridgewell/trace-mapping" "^0.3.17" jsesc "^2.5.1" -"@babel/helper-compilation-targets@^7.21.5": - version "7.21.5" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.21.5.tgz#631e6cc784c7b660417421349aac304c94115366" - integrity sha512-1RkbFGUKex4lvsB9yhIfWltJM5cZKUftB2eNajaDv3dCMEp49iBG0K14uH8NnX9IPux2+mK7JGEOB0jn48/J6w== +"@babel/helper-compilation-targets@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz#0698fc44551a26cf29f18d4662d5bf545a6cfc52" + integrity sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw== dependencies: - "@babel/compat-data" "^7.21.5" - "@babel/helper-validator-option" "^7.21.0" - browserslist "^4.21.3" + "@babel/compat-data" "^7.22.9" + "@babel/helper-validator-option" "^7.22.15" + browserslist "^4.21.9" lru-cache "^5.1.1" - semver "^6.3.0" + semver "^6.3.1" -"@babel/helper-environment-visitor@^7.21.5": - version "7.21.5" - resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.21.5.tgz#c769afefd41d171836f7cb63e295bedf689d48ba" - integrity sha512-IYl4gZ3ETsWocUWgsFZLM5i1BYx9SoemminVEXadgLBa9TdeorzgLKm8wWLA6J1N/kT3Kch8XIk1laNzYoHKvQ== +"@babel/helper-environment-visitor@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167" + integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA== -"@babel/helper-function-name@^7.21.0": - version "7.21.0" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz#d552829b10ea9f120969304023cd0645fa00b1b4" - integrity sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg== +"@babel/helper-function-name@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759" + integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw== dependencies: - "@babel/template" "^7.20.7" - "@babel/types" "^7.21.0" + "@babel/template" "^7.22.15" + "@babel/types" "^7.23.0" -"@babel/helper-hoist-variables@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz#d4d2c8fb4baeaa5c68b99cc8245c56554f926678" - integrity sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q== +"@babel/helper-hoist-variables@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb" + integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw== dependencies: - "@babel/types" "^7.18.6" + "@babel/types" "^7.22.5" -"@babel/helper-module-imports@^7.21.4": - version "7.21.4" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.21.4.tgz#ac88b2f76093637489e718a90cec6cf8a9b029af" - integrity sha512-orajc5T2PsRYUN3ZryCEFeMDYwyw09c/pZeaQEZPH0MpKzSvn3e0uXsDBu3k03VI+9DBiRo+l22BfKTpKwa/Wg== +"@babel/helper-module-imports@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz#16146307acdc40cc00c3b2c647713076464bdbf0" + integrity sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w== dependencies: - "@babel/types" "^7.21.4" + "@babel/types" "^7.22.15" -"@babel/helper-module-transforms@^7.21.5": - version "7.21.5" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.21.5.tgz#d937c82e9af68d31ab49039136a222b17ac0b420" - integrity sha512-bI2Z9zBGY2q5yMHoBvJ2a9iX3ZOAzJPm7Q8Yz6YeoUjU/Cvhmi2G4QyTNyPBqqXSgTjUxRg3L0xV45HvkNWWBw== +"@babel/helper-module-transforms@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.23.0.tgz#3ec246457f6c842c0aee62a01f60739906f7047e" + integrity sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw== dependencies: - "@babel/helper-environment-visitor" "^7.21.5" - "@babel/helper-module-imports" "^7.21.4" - "@babel/helper-simple-access" "^7.21.5" - "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/helper-validator-identifier" "^7.19.1" - "@babel/template" "^7.20.7" - "@babel/traverse" "^7.21.5" - "@babel/types" "^7.21.5" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-module-imports" "^7.22.15" + "@babel/helper-simple-access" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + "@babel/helper-validator-identifier" "^7.22.20" -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.20.2", "@babel/helper-plugin-utils@^7.8.0": - version "7.21.5" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.21.5.tgz#345f2377d05a720a4e5ecfa39cbf4474a4daed56" - integrity sha512-0WDaIlXKOX/3KfBK/dwP1oQGiPh6rjMkT7HIRv7i5RR2VUMwrx5ZL0dwBkKx7+SW1zwNdgjHd34IMk5ZjTeHVg== +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.8.0": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz#dd7ee3735e8a313b9f7b05a773d892e88e6d7295" + integrity sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg== -"@babel/helper-simple-access@^7.21.5": - version "7.21.5" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.21.5.tgz#d697a7971a5c39eac32c7e63c0921c06c8a249ee" - integrity sha512-ENPDAMC1wAjR0uaCUwliBdiSl1KBJAVnMTzXqi64c2MG8MPR6ii4qf7bSXDqSFbr4W6W028/rf5ivoHop5/mkg== +"@babel/helper-simple-access@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz#4938357dc7d782b80ed6dbb03a0fba3d22b1d5de" + integrity sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w== dependencies: - "@babel/types" "^7.21.5" + "@babel/types" "^7.22.5" -"@babel/helper-split-export-declaration@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz#7367949bc75b20c6d5a5d4a97bba2824ae8ef075" - integrity sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA== +"@babel/helper-split-export-declaration@^7.22.6": + version "7.22.6" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c" + integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g== dependencies: - "@babel/types" "^7.18.6" + "@babel/types" "^7.22.5" -"@babel/helper-string-parser@^7.21.5": - version "7.21.5" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.21.5.tgz#2b3eea65443c6bdc31c22d037c65f6d323b6b2bd" - integrity sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w== +"@babel/helper-string-parser@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f" + integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw== -"@babel/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.19.1": - version "7.19.1" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2" - integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== +"@babel/helper-validator-identifier@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" + integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== -"@babel/helper-validator-option@^7.21.0": - version "7.21.0" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz#8224c7e13ace4bafdc4004da2cf064ef42673180" - integrity sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ== +"@babel/helper-validator-option@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz#694c30dfa1d09a6534cdfcafbe56789d36aba040" + integrity sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA== -"@babel/helpers@^7.21.5": - version "7.21.5" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.21.5.tgz#5bac66e084d7a4d2d9696bdf0175a93f7fb63c08" - integrity sha512-BSY+JSlHxOmGsPTydUkPf1MdMQ3M81x5xGCOVgWM3G8XH77sJ292Y2oqcp0CbbgxhqBuI46iUz1tT7hqP7EfgA== +"@babel/helpers@^7.23.0": + version "7.23.1" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.23.1.tgz#44e981e8ce2b9e99f8f0b703f3326a4636c16d15" + integrity sha512-chNpneuK18yW5Oxsr+t553UZzzAs3aZnFm4bxhebsNTeshrC95yA7l5yl7GBAG+JG1rF0F7zzD2EixK9mWSDoA== dependencies: - "@babel/template" "^7.20.7" - "@babel/traverse" "^7.21.5" - "@babel/types" "^7.21.5" + "@babel/template" "^7.22.15" + "@babel/traverse" "^7.23.0" + "@babel/types" "^7.23.0" -"@babel/highlight@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf" - integrity sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g== +"@babel/highlight@^7.22.13": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.20.tgz#4ca92b71d80554b01427815e06f2df965b9c1f54" + integrity sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg== dependencies: - "@babel/helper-validator-identifier" "^7.18.6" - chalk "^2.0.0" + "@babel/helper-validator-identifier" "^7.22.20" + chalk "^2.4.2" js-tokens "^4.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.21.5", "@babel/parser@^7.21.8": - version "7.21.8" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.8.tgz#642af7d0333eab9c0ad70b14ac5e76dbde7bfdf8" - integrity sha512-6zavDGdzG3gUqAdWvlLFfk+36RilI+Pwyuuh7HItyeScCWP3k6i8vKclAQ0bM/0y/Kz/xiwvxhMv9MgTJP5gmA== +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.22.15", "@babel/parser@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.0.tgz#da950e622420bf96ca0d0f2909cdddac3acd8719" + integrity sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw== "@babel/plugin-syntax-async-generators@^7.8.4": version "7.8.4" @@ -203,11 +201,11 @@ "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-jsx@^7.7.2": - version "7.21.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.21.4.tgz#f264ed7bf40ffc9ec239edabc17a50c4f5b6fea2" - integrity sha512-5hewiLct5OKyh6PLKEYaFclcqtIgCb6bmELouxjF6up5q3Sov7rOayW4RwhbaBL0dit8rA80GNfY+UuDp2mBbQ== + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz#a6b68e84fb76e759fc3b93e901876ffabbe1d918" + integrity sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg== dependencies: - "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-logical-assignment-operators@^7.8.3": version "7.10.4" @@ -259,44 +257,44 @@ "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-syntax-typescript@^7.7.2": - version "7.21.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.21.4.tgz#2751948e9b7c6d771a8efa59340c15d4a2891ff8" - integrity sha512-xz0D39NvhQn4t4RNsHmDnnsaQizIlUkdtYvLs8La1BlfjQ6JEwxkJGeqJMW2tAXx+q6H+WFuUTXNdYVpEya0YA== + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.22.5.tgz#aac8d383b062c5072c647a31ef990c1d0af90272" + integrity sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ== dependencies: - "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-plugin-utils" "^7.22.5" -"@babel/template@^7.20.7", "@babel/template@^7.3.3": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.20.7.tgz#a15090c2839a83b02aa996c0b4994005841fd5a8" - integrity sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw== +"@babel/template@^7.22.15", "@babel/template@^7.3.3": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38" + integrity sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w== dependencies: - "@babel/code-frame" "^7.18.6" - "@babel/parser" "^7.20.7" - "@babel/types" "^7.20.7" + "@babel/code-frame" "^7.22.13" + "@babel/parser" "^7.22.15" + "@babel/types" "^7.22.15" -"@babel/traverse@^7.21.5", "@babel/traverse@^7.7.2": - version "7.21.5" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.21.5.tgz#ad22361d352a5154b498299d523cf72998a4b133" - integrity sha512-AhQoI3YjWi6u/y/ntv7k48mcrCXmus0t79J9qPNlk/lAsFlCiJ047RmbfMOawySTHtywXhbXgpx/8nXMYd+oFw== +"@babel/traverse@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.0.tgz#18196ddfbcf4ccea324b7f6d3ada00d8c5a99c53" + integrity sha512-t/QaEvyIoIkwzpiZ7aoSKK8kObQYeF7T2v+dazAYCb8SXtp58zEVkWW7zAnju8FNKNdr4ScAOEDmMItbyOmEYw== dependencies: - "@babel/code-frame" "^7.21.4" - "@babel/generator" "^7.21.5" - "@babel/helper-environment-visitor" "^7.21.5" - "@babel/helper-function-name" "^7.21.0" - "@babel/helper-hoist-variables" "^7.18.6" - "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/parser" "^7.21.5" - "@babel/types" "^7.21.5" + "@babel/code-frame" "^7.22.13" + "@babel/generator" "^7.23.0" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-function-name" "^7.23.0" + "@babel/helper-hoist-variables" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + "@babel/parser" "^7.23.0" + "@babel/types" "^7.23.0" debug "^4.1.0" globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.18.6", "@babel/types@^7.20.7", "@babel/types@^7.21.0", "@babel/types@^7.21.4", "@babel/types@^7.21.5", "@babel/types@^7.3.0", "@babel/types@^7.3.3": - version "7.21.5" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.21.5.tgz#18dfbd47c39d3904d5db3d3dc2cc80bedb60e5b6" - integrity sha512-m4AfNvVF2mVC/F7fDEdH2El3HzUg9It/XsCxZiOTTA3m3qYfcSVSbTfM6Q9xG+hYDniZssYhlXKKUMD5m8tF4Q== +"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.22.15", "@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.3.3": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.0.tgz#8c1f020c9df0e737e4e247c0619f58c68458aaeb" + integrity sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg== dependencies: - "@babel/helper-string-parser" "^7.21.5" - "@babel/helper-validator-identifier" "^7.19.1" + "@babel/helper-string-parser" "^7.22.5" + "@babel/helper-validator-identifier" "^7.22.20" to-fast-properties "^2.0.0" "@bcoe/v8-coverage@^0.2.3": @@ -311,19 +309,19 @@ dependencies: eslint-visitor-keys "^3.3.0" -"@eslint-community/regexpp@^4.4.0": - version "4.5.1" - resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.5.1.tgz#cdd35dce4fa1a89a4fd42b1599eb35b3af408884" - integrity sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ== +"@eslint-community/regexpp@^4.4.0", "@eslint-community/regexpp@^4.6.1": + version "4.9.1" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.9.1.tgz#449dfa81a57a1d755b09aa58d826c1262e4283b4" + integrity sha512-Y27x+MBLjXa+0JWDhykM3+JE+il3kHKAEqabfEWq3SDhZjLYb6/BHL/JKFnH3fe207JaXkyDo685Oc2Glt6ifA== -"@eslint/eslintrc@^2.0.3": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.0.3.tgz#4910db5505f4d503f27774bf356e3704818a0331" - integrity sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ== +"@eslint/eslintrc@^2.1.2": + version "2.1.2" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.2.tgz#c6936b4b328c64496692f76944e755738be62396" + integrity sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g== dependencies: ajv "^6.12.4" debug "^4.3.2" - espree "^9.5.2" + espree "^9.6.0" globals "^13.19.0" ignore "^5.2.0" import-fresh "^3.2.1" @@ -331,15 +329,15 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@eslint/js@8.40.0": - version "8.40.0" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.40.0.tgz#3ba73359e11f5a7bd3e407f70b3528abfae69cec" - integrity sha512-ElyB54bJIhXQYVKjDSvCkPO1iU1tSAeVQJbllWJq1XQSmmA4dgFk8CbiBGpiOPxleE48vDogxCtmMYku4HSVLA== +"@eslint/js@8.50.0": + version "8.50.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.50.0.tgz#9e93b850f0f3fa35f5fa59adfd03adae8488e484" + integrity sha512-NCC3zz2+nvYd+Ckfh87rA47zfu2QsQpvc6k1yzTk+b9KzRj0wkGa8LSoGOXN6Zv4lRf/EIoZ80biDh9HOI+RNQ== -"@humanwhocodes/config-array@^0.11.8": - version "0.11.8" - resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.8.tgz#03595ac2075a4dc0f191cc2131de14fbd7d410b9" - integrity sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g== +"@humanwhocodes/config-array@^0.11.11": + version "0.11.11" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.11.tgz#88a04c570dbbc7dd943e4712429c3df09bc32844" + integrity sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA== dependencies: "@humanwhocodes/object-schema" "^1.2.1" debug "^4.1.1" @@ -371,110 +369,110 @@ resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== -"@jest/console@^29.5.0": - version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.5.0.tgz#593a6c5c0d3f75689835f1b3b4688c4f8544cb57" - integrity sha512-NEpkObxPwyw/XxZVLPmAGKE89IQRp4puc6IQRPru6JKd1M3fW9v1xM1AnzIJE65hbCkzQAdnL8P47e9hzhiYLQ== +"@jest/console@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.7.0.tgz#cd4822dbdb84529265c5a2bdb529a3c9cc950ffc" + integrity sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg== dependencies: - "@jest/types" "^29.5.0" + "@jest/types" "^29.6.3" "@types/node" "*" chalk "^4.0.0" - jest-message-util "^29.5.0" - jest-util "^29.5.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" slash "^3.0.0" -"@jest/core@^29.5.0": - version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.5.0.tgz#76674b96904484e8214614d17261cc491e5f1f03" - integrity sha512-28UzQc7ulUrOQw1IsN/kv1QES3q2kkbl/wGslyhAclqZ/8cMdB5M68BffkIdSJgKBUt50d3hbwJ92XESlE7LiQ== +"@jest/core@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.7.0.tgz#b6cccc239f30ff36609658c5a5e2291757ce448f" + integrity sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg== dependencies: - "@jest/console" "^29.5.0" - "@jest/reporters" "^29.5.0" - "@jest/test-result" "^29.5.0" - "@jest/transform" "^29.5.0" - "@jest/types" "^29.5.0" + "@jest/console" "^29.7.0" + "@jest/reporters" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" "@types/node" "*" ansi-escapes "^4.2.1" chalk "^4.0.0" ci-info "^3.2.0" exit "^0.1.2" graceful-fs "^4.2.9" - jest-changed-files "^29.5.0" - jest-config "^29.5.0" - jest-haste-map "^29.5.0" - jest-message-util "^29.5.0" - jest-regex-util "^29.4.3" - jest-resolve "^29.5.0" - jest-resolve-dependencies "^29.5.0" - jest-runner "^29.5.0" - jest-runtime "^29.5.0" - jest-snapshot "^29.5.0" - jest-util "^29.5.0" - jest-validate "^29.5.0" - jest-watcher "^29.5.0" + jest-changed-files "^29.7.0" + jest-config "^29.7.0" + jest-haste-map "^29.7.0" + jest-message-util "^29.7.0" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-resolve-dependencies "^29.7.0" + jest-runner "^29.7.0" + jest-runtime "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + jest-watcher "^29.7.0" micromatch "^4.0.4" - pretty-format "^29.5.0" + pretty-format "^29.7.0" slash "^3.0.0" strip-ansi "^6.0.0" -"@jest/environment@^29.5.0": - version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.5.0.tgz#9152d56317c1fdb1af389c46640ba74ef0bb4c65" - integrity sha512-5FXw2+wD29YU1d4I2htpRX7jYnAyTRjP2CsXQdo9SAM8g3ifxWPSV0HnClSn71xwctr0U3oZIIH+dtbfmnbXVQ== +"@jest/environment@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.7.0.tgz#24d61f54ff1f786f3cd4073b4b94416383baf2a7" + integrity sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw== dependencies: - "@jest/fake-timers" "^29.5.0" - "@jest/types" "^29.5.0" + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" "@types/node" "*" - jest-mock "^29.5.0" + jest-mock "^29.7.0" -"@jest/expect-utils@^29.5.0": - version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.5.0.tgz#f74fad6b6e20f924582dc8ecbf2cb800fe43a036" - integrity sha512-fmKzsidoXQT2KwnrwE0SQq3uj8Z763vzR8LnLBwC2qYWEFpjX8daRsk6rHUM1QvNlEW/UJXNXm59ztmJJWs2Mg== +"@jest/expect-utils@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.7.0.tgz#023efe5d26a8a70f21677d0a1afc0f0a44e3a1c6" + integrity sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA== dependencies: - jest-get-type "^29.4.3" + jest-get-type "^29.6.3" -"@jest/expect@^29.5.0": - version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.5.0.tgz#80952f5316b23c483fbca4363ce822af79c38fba" - integrity sha512-PueDR2HGihN3ciUNGr4uelropW7rqUfTiOn+8u0leg/42UhblPxHkfoh0Ruu3I9Y1962P3u2DY4+h7GVTSVU6g== +"@jest/expect@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.7.0.tgz#76a3edb0cb753b70dfbfe23283510d3d45432bf2" + integrity sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ== dependencies: - expect "^29.5.0" - jest-snapshot "^29.5.0" + expect "^29.7.0" + jest-snapshot "^29.7.0" -"@jest/fake-timers@^29.5.0": - version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.5.0.tgz#d4d09ec3286b3d90c60bdcd66ed28d35f1b4dc2c" - integrity sha512-9ARvuAAQcBwDAqOnglWq2zwNIRUDtk/SCkp/ToGEhFv5r86K21l+VEs0qNTaXtyiY0lEePl3kylijSYJQqdbDg== +"@jest/fake-timers@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.7.0.tgz#fd91bf1fffb16d7d0d24a426ab1a47a49881a565" + integrity sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ== dependencies: - "@jest/types" "^29.5.0" + "@jest/types" "^29.6.3" "@sinonjs/fake-timers" "^10.0.2" "@types/node" "*" - jest-message-util "^29.5.0" - jest-mock "^29.5.0" - jest-util "^29.5.0" + jest-message-util "^29.7.0" + jest-mock "^29.7.0" + jest-util "^29.7.0" -"@jest/globals@^29.5.0": - version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.5.0.tgz#6166c0bfc374c58268677539d0c181f9c1833298" - integrity sha512-S02y0qMWGihdzNbUiqSAiKSpSozSuHX5UYc7QbnHP+D9Lyw8DgGGCinrN9uSuHPeKgSSzvPom2q1nAtBvUsvPQ== +"@jest/globals@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.7.0.tgz#8d9290f9ec47ff772607fa864ca1d5a2efae1d4d" + integrity sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ== dependencies: - "@jest/environment" "^29.5.0" - "@jest/expect" "^29.5.0" - "@jest/types" "^29.5.0" - jest-mock "^29.5.0" + "@jest/environment" "^29.7.0" + "@jest/expect" "^29.7.0" + "@jest/types" "^29.6.3" + jest-mock "^29.7.0" -"@jest/reporters@^29.5.0": - version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.5.0.tgz#985dfd91290cd78ddae4914ba7921bcbabe8ac9b" - integrity sha512-D05STXqj/M8bP9hQNSICtPqz97u7ffGzZu+9XLucXhkOFBqKcXe04JLZOgIekOxdb73MAoBUFnqvf7MCpKk5OA== +"@jest/reporters@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.7.0.tgz#04b262ecb3b8faa83b0b3d321623972393e8f4c7" + integrity sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg== dependencies: "@bcoe/v8-coverage" "^0.2.3" - "@jest/console" "^29.5.0" - "@jest/test-result" "^29.5.0" - "@jest/transform" "^29.5.0" - "@jest/types" "^29.5.0" - "@jridgewell/trace-mapping" "^0.3.15" + "@jest/console" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@jridgewell/trace-mapping" "^0.3.18" "@types/node" "*" chalk "^4.0.0" collect-v8-coverage "^1.0.0" @@ -482,88 +480,81 @@ glob "^7.1.3" graceful-fs "^4.2.9" istanbul-lib-coverage "^3.0.0" - istanbul-lib-instrument "^5.1.0" + istanbul-lib-instrument "^6.0.0" istanbul-lib-report "^3.0.0" istanbul-lib-source-maps "^4.0.0" istanbul-reports "^3.1.3" - jest-message-util "^29.5.0" - jest-util "^29.5.0" - jest-worker "^29.5.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + jest-worker "^29.7.0" slash "^3.0.0" string-length "^4.0.1" strip-ansi "^6.0.0" v8-to-istanbul "^9.0.1" -"@jest/schemas@^29.4.3": - version "29.4.3" - resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.4.3.tgz#39cf1b8469afc40b6f5a2baaa146e332c4151788" - integrity sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg== - dependencies: - "@sinclair/typebox" "^0.25.16" - -"@jest/schemas@^29.6.0": - version "29.6.0" - resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.0.tgz#0f4cb2c8e3dca80c135507ba5635a4fd755b0040" - integrity sha512-rxLjXyJBTL4LQeJW3aKo0M/+GkCOXsO+8i9Iu7eDb6KwtP65ayoDsitrdPBtujxQ88k4wI2FNYfa6TOGwSn6cQ== +"@jest/schemas@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" + integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== dependencies: "@sinclair/typebox" "^0.27.8" -"@jest/source-map@^29.4.3": - version "29.4.3" - resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.4.3.tgz#ff8d05cbfff875d4a791ab679b4333df47951d20" - integrity sha512-qyt/mb6rLyd9j1jUts4EQncvS6Yy3PM9HghnNv86QBlV+zdL2inCdK1tuVlL+J+lpiw2BI67qXOrX3UurBqQ1w== +"@jest/source-map@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.6.3.tgz#d90ba772095cf37a34a5eb9413f1b562a08554c4" + integrity sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw== dependencies: - "@jridgewell/trace-mapping" "^0.3.15" + "@jridgewell/trace-mapping" "^0.3.18" callsites "^3.0.0" graceful-fs "^4.2.9" -"@jest/test-result@^29.5.0": - version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.5.0.tgz#7c856a6ca84f45cc36926a4e9c6b57f1973f1408" - integrity sha512-fGl4rfitnbfLsrfx1uUpDEESS7zM8JdgZgOCQuxQvL1Sn/I6ijeAVQWGfXI9zb1i9Mzo495cIpVZhA0yr60PkQ== +"@jest/test-result@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.7.0.tgz#8db9a80aa1a097bb2262572686734baed9b1657c" + integrity sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA== dependencies: - "@jest/console" "^29.5.0" - "@jest/types" "^29.5.0" + "@jest/console" "^29.7.0" + "@jest/types" "^29.6.3" "@types/istanbul-lib-coverage" "^2.0.0" collect-v8-coverage "^1.0.0" -"@jest/test-sequencer@^29.5.0": - version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.5.0.tgz#34d7d82d3081abd523dbddc038a3ddcb9f6d3cc4" - integrity sha512-yPafQEcKjkSfDXyvtgiV4pevSeyuA6MQr6ZIdVkWJly9vkqjnFfcfhRQqpD5whjoU8EORki752xQmjaqoFjzMQ== +"@jest/test-sequencer@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz#6cef977ce1d39834a3aea887a1726628a6f072ce" + integrity sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw== dependencies: - "@jest/test-result" "^29.5.0" + "@jest/test-result" "^29.7.0" graceful-fs "^4.2.9" - jest-haste-map "^29.5.0" + jest-haste-map "^29.7.0" slash "^3.0.0" -"@jest/transform@^29.5.0": - version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.5.0.tgz#cf9c872d0965f0cbd32f1458aa44a2b1988b00f9" - integrity sha512-8vbeZWqLJOvHaDfeMuoHITGKSz5qWc9u04lnWrQE3VyuSw604PzQM824ZeX9XSjUCeDiE3GuxZe5UKa8J61NQw== +"@jest/transform@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.7.0.tgz#df2dd9c346c7d7768b8a06639994640c642e284c" + integrity sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw== dependencies: "@babel/core" "^7.11.6" - "@jest/types" "^29.5.0" - "@jridgewell/trace-mapping" "^0.3.15" + "@jest/types" "^29.6.3" + "@jridgewell/trace-mapping" "^0.3.18" babel-plugin-istanbul "^6.1.1" chalk "^4.0.0" convert-source-map "^2.0.0" fast-json-stable-stringify "^2.1.0" graceful-fs "^4.2.9" - jest-haste-map "^29.5.0" - jest-regex-util "^29.4.3" - jest-util "^29.5.0" + jest-haste-map "^29.7.0" + jest-regex-util "^29.6.3" + jest-util "^29.7.0" micromatch "^4.0.4" pirates "^4.0.4" slash "^3.0.0" write-file-atomic "^4.0.2" -"@jest/types@^29.5.0": - version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.5.0.tgz#f59ef9b031ced83047c67032700d8c807d6e1593" - integrity sha512-qbu7kN6czmVRc3xWFQcAN03RAUamgppVUdXrvl1Wr3jlNF93o9mJbGcDWrwGB6ht44u7efB1qCFgVQmca24Uog== +"@jest/types@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59" + integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw== dependencies: - "@jest/schemas" "^29.4.3" + "@jest/schemas" "^29.6.3" "@types/istanbul-lib-coverage" "^2.0.0" "@types/istanbul-reports" "^3.0.0" "@types/node" "*" @@ -579,33 +570,28 @@ "@jridgewell/sourcemap-codec" "^1.4.10" "@jridgewell/trace-mapping" "^0.3.9" -"@jridgewell/resolve-uri@3.1.0": - version "3.1.0" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" - integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== +"@jridgewell/resolve-uri@^3.1.0": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721" + integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA== "@jridgewell/set-array@^1.0.1": version "1.1.2" resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== -"@jridgewell/sourcemap-codec@1.4.14": - version "1.4.14" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" - integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== - -"@jridgewell/sourcemap-codec@^1.4.10": +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": version "1.4.15" resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== -"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.15", "@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.9": - version "0.3.18" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz#25783b2086daf6ff1dcb53c9249ae480e4dd4cd6" - integrity sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA== +"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.9": + version "0.3.19" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz#f8a3249862f91be48d3127c3cfe992f79b4b8811" + integrity sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw== dependencies: - "@jridgewell/resolve-uri" "3.1.0" - "@jridgewell/sourcemap-codec" "1.4.14" + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" "@nodelib/fs.scandir@2.1.5": version "2.1.5" @@ -628,34 +614,29 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@sinclair/typebox@^0.25.16": - version "0.25.24" - resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.25.24.tgz#8c7688559979f7079aacaf31aa881c3aa410b718" - integrity sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ== - "@sinclair/typebox@^0.27.8": version "0.27.8" resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== -"@sinonjs/commons@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-2.0.0.tgz#fd4ca5b063554307e8327b4564bd56d3b73924a3" - integrity sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg== +"@sinonjs/commons@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.0.tgz#beb434fe875d965265e04722ccfc21df7f755d72" + integrity sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA== dependencies: type-detect "4.0.8" "@sinonjs/fake-timers@^10.0.2": - version "10.0.2" - resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-10.0.2.tgz#d10549ed1f423d80639c528b6c7f5a1017747d0c" - integrity sha512-SwUDyjWnah1AaNl7kxsa7cfLhlTYoiyhDAIgyh+El30YvXs/o7OLXpYH88Zdhyx9JExKrmHDJ+10bwIcY80Jmw== + version "10.3.0" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz#55fdff1ecab9f354019129daf4df0dd4d923ea66" + integrity sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA== dependencies: - "@sinonjs/commons" "^2.0.0" + "@sinonjs/commons" "^3.0.0" "@types/babel__core@^7.1.14": - version "7.20.0" - resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.0.tgz#61bc5a4cae505ce98e1e36c5445e4bee060d8891" - integrity sha512-+n8dL/9GWblDO0iU6eZAwEIJVr5DWigtle+Q6HLOrh/pdbXOhOtqzq8VPPE2zvNJzSKY4vH/z3iT3tn0A3ypiQ== + version "7.20.2" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.2.tgz#215db4f4a35d710256579784a548907237728756" + integrity sha512-pNpr1T1xLUc2l3xJKuPtsEky3ybxN3m4fJkknfIpTCTfIZCDW57oAg+EfCgIIp2rvCe0Wn++/FfodDS4YXxBwA== dependencies: "@babel/parser" "^7.20.7" "@babel/types" "^7.20.7" @@ -664,31 +645,31 @@ "@types/babel__traverse" "*" "@types/babel__generator@*": - version "7.6.4" - resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.4.tgz#1f20ce4c5b1990b37900b63f050182d28c2439b7" - integrity sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg== + version "7.6.5" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.5.tgz#281f4764bcbbbc51fdded0f25aa587b4ce14da95" + integrity sha512-h9yIuWbJKdOPLJTbmSpPzkF67e659PbQDba7ifWm5BJ8xTv+sDmS7rFmywkWOvXedGTivCdeGSIIX8WLcRTz8w== dependencies: "@babel/types" "^7.0.0" "@types/babel__template@*": - version "7.4.1" - resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.1.tgz#3d1a48fd9d6c0edfd56f2ff578daed48f36c8969" - integrity sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g== + version "7.4.2" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.2.tgz#843e9f1f47c957553b0c374481dc4772921d6a6b" + integrity sha512-/AVzPICMhMOMYoSx9MoKpGDKdBRsIXMNByh1PXSZoa+v6ZoLa8xxtsT/uLQ/NJm0XVAWl/BvId4MlDeXJaeIZQ== dependencies: "@babel/parser" "^7.1.0" "@babel/types" "^7.0.0" "@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": - version "7.18.5" - resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.18.5.tgz#c107216842905afafd3b6e774f6f935da6f5db80" - integrity sha512-enCvTL8m/EHS/zIvJno9nE+ndYPh1/oNFzRYRmtUqJICG2VnCSBzMLW5VN2KCQU91f23tsNKR8v7VJJQMatl7Q== + version "7.20.2" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.2.tgz#4ddf99d95cfdd946ff35d2b65c978d9c9bf2645d" + integrity sha512-ojlGK1Hsfce93J0+kn3H5R73elidKUaZonirN33GSmgTUMpzI/MIFfSpF3haANe3G1bEBS9/9/QEqwTzwqFsKw== dependencies: - "@babel/types" "^7.3.0" + "@babel/types" "^7.20.7" "@types/graceful-fs@^4.1.3": - version "4.1.6" - resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.6.tgz#e14b2576a1c25026b7f02ede1de3b84c3a1efeae" - integrity sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw== + version "4.1.7" + resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.7.tgz#30443a2e64fd51113bc3e2ba0914d47109695e2a" + integrity sha512-MhzcwU8aUygZroVwL2jeYk6JisJrPl/oov/gsgGCue9mkgl9wjGbzReYQClxiUgFDnib9FuHqTndccKeZKxTRw== dependencies: "@types/node" "*" @@ -698,46 +679,41 @@ integrity sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g== "@types/istanbul-lib-report@*": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#c14c24f18ea8190c118ee7562b7ff99a36552686" - integrity sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg== + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#412e0725ef41cde73bfa03e0e833eaff41e0fd63" + integrity sha512-gPQuzaPR5h/djlAv2apEG1HVOyj1IUs7GpfMZixU0/0KXT3pm64ylHuMUI1/Akh+sq/iikxg6Z2j+fcMDXaaTQ== dependencies: "@types/istanbul-lib-coverage" "*" "@types/istanbul-reports@^3.0.0": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz#9153fe98bba2bd565a63add9436d6f0d7f8468ff" - integrity sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw== + version "3.0.2" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.2.tgz#edc8e421991a3b4df875036d381fc0a5a982f549" + integrity sha512-kv43F9eb3Lhj+lr/Hn6OcLCs/sSM8bt+fIaP11rCYngfV6NVjzWXJ17owQtDQTL9tQ8WSLUrGsSJ6rJz0F1w1A== dependencies: "@types/istanbul-lib-report" "*" "@types/jest@^29.5.1": - version "29.5.1" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.1.tgz#83c818aa9a87da27d6da85d3378e5a34d2f31a47" - integrity sha512-tEuVcHrpaixS36w7hpsfLBLpjtMRJUE09/MHXn923LOVojDwyC14cWcfc0rDs0VEfUyYmt/+iX1kxxp+gZMcaQ== + version "29.5.5" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.5.tgz#727204e06228fe24373df9bae76b90f3e8236a2a" + integrity sha512-ebylz2hnsWR9mYvmBFbXJXr+33UPc4+ZdxyDXh5w0FlPBTfCVN3wPL+kuOiQt3xvrK419v7XWeAs+AeOksafXg== dependencies: expect "^29.0.0" pretty-format "^29.0.0" "@types/json-schema@^7.0.9": - version "7.0.11" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" - integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== + version "7.0.13" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.13.tgz#02c24f4363176d2d18fc8b70b9f3c54aba178a85" + integrity sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ== "@types/node@*", "@types/node@^20.1.2": - version "20.1.2" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.1.2.tgz#8fd63447e3f99aba6c3168fd2ec4580d5b97886f" - integrity sha512-CTO/wa8x+rZU626cL2BlbCDzydgnFNgc19h4YvizpTO88MFQxab8wqisxaofQJ/9bLGugRdWIuX/TbIs6VVF6g== - -"@types/prettier@^2.1.5": - version "2.7.2" - resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.7.2.tgz#6c2324641cc4ba050a8c710b2b251b377581fbf0" - integrity sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg== + version "20.8.0" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.8.0.tgz#10ddf0119cf20028781c06d7115562934e53f745" + integrity sha512-LzcWltT83s1bthcvjBmiBvGJiiUe84NWRHkw+ZV6Fr41z2FbIzvc815dk2nQ3RAKMuN2fkenM/z3Xv2QzEpYxQ== "@types/semver@^7.3.12": - version "7.5.0" - resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.0.tgz#591c1ce3a702c45ee15f47a42ade72c2fd78978a" - integrity sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw== + version "7.5.3" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.3.tgz#9a726e116beb26c24f1ccd6850201e1246122e04" + integrity sha512-OxepLK9EuNEIPxWNME+C6WwbRAOOI2o2BaQEGzz5Lu2e4Z5eDnEo+/aVEDMIXywoJitJ7xWd641wrGLZdtwRyw== "@types/stack-utils@^2.0.0": version "2.0.1" @@ -745,99 +721,99 @@ integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== "@types/yargs-parser@*": - version "21.0.0" - resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b" - integrity sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA== + version "21.0.1" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.1.tgz#07773d7160494d56aa882d7531aac7319ea67c3b" + integrity sha512-axdPBuLuEJt0c4yI5OZssC19K2Mq1uKdrfZBzuxLvaztgqUtFYZUNw7lETExPYJR9jdEoIg4mb7RQKRQzOkeGQ== "@types/yargs@^17.0.8": - version "17.0.24" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.24.tgz#b3ef8d50ad4aa6aecf6ddc97c580a00f5aa11902" - integrity sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw== + version "17.0.26" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.26.tgz#388e5002a8b284ad7b4599ba89920a6d74d8d79a" + integrity sha512-Y3vDy2X6zw/ZCumcwLpdhM5L7jmyGpmBCTYMHDLqT2IKVMYRRLdv6ZakA+wxhra6Z/3bwhNbNl9bDGXaFU+6rw== dependencies: "@types/yargs-parser" "*" "@typescript-eslint/eslint-plugin@^5.59.5": - version "5.59.5" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.5.tgz#f156827610a3f8cefc56baeaa93cd4a5f32966b4" - integrity sha512-feA9xbVRWJZor+AnLNAr7A8JRWeZqHUf4T9tlP+TN04b05pFVhO5eN7/O93Y/1OUlLMHKbnJisgDURs/qvtqdg== + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz#aeef0328d172b9e37d9bab6dbc13b87ed88977db" + integrity sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag== dependencies: "@eslint-community/regexpp" "^4.4.0" - "@typescript-eslint/scope-manager" "5.59.5" - "@typescript-eslint/type-utils" "5.59.5" - "@typescript-eslint/utils" "5.59.5" + "@typescript-eslint/scope-manager" "5.62.0" + "@typescript-eslint/type-utils" "5.62.0" + "@typescript-eslint/utils" "5.62.0" debug "^4.3.4" - grapheme-splitter "^1.0.4" + graphemer "^1.4.0" ignore "^5.2.0" natural-compare-lite "^1.4.0" semver "^7.3.7" tsutils "^3.21.0" "@typescript-eslint/parser@^5.59.5": - version "5.59.5" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.59.5.tgz#63064f5eafbdbfb5f9dfbf5c4503cdf949852981" - integrity sha512-NJXQC4MRnF9N9yWqQE2/KLRSOLvrrlZb48NGVfBa+RuPMN6B7ZcK5jZOvhuygv4D64fRKnZI4L4p8+M+rfeQuw== + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.62.0.tgz#1b63d082d849a2fcae8a569248fbe2ee1b8a56c7" + integrity sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA== dependencies: - "@typescript-eslint/scope-manager" "5.59.5" - "@typescript-eslint/types" "5.59.5" - "@typescript-eslint/typescript-estree" "5.59.5" + "@typescript-eslint/scope-manager" "5.62.0" + "@typescript-eslint/types" "5.62.0" + "@typescript-eslint/typescript-estree" "5.62.0" debug "^4.3.4" -"@typescript-eslint/scope-manager@5.59.5": - version "5.59.5" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.59.5.tgz#33ffc7e8663f42cfaac873de65ebf65d2bce674d" - integrity sha512-jVecWwnkX6ZgutF+DovbBJirZcAxgxC0EOHYt/niMROf8p4PwxxG32Qdhj/iIQQIuOflLjNkxoXyArkcIP7C3A== +"@typescript-eslint/scope-manager@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz#d9457ccc6a0b8d6b37d0eb252a23022478c5460c" + integrity sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w== dependencies: - "@typescript-eslint/types" "5.59.5" - "@typescript-eslint/visitor-keys" "5.59.5" + "@typescript-eslint/types" "5.62.0" + "@typescript-eslint/visitor-keys" "5.62.0" -"@typescript-eslint/type-utils@5.59.5": - version "5.59.5" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.59.5.tgz#485b0e2c5b923460bc2ea6b338c595343f06fc9b" - integrity sha512-4eyhS7oGym67/pSxA2mmNq7X164oqDYNnZCUayBwJZIRVvKpBCMBzFnFxjeoDeShjtO6RQBHBuwybuX3POnDqg== +"@typescript-eslint/type-utils@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz#286f0389c41681376cdad96b309cedd17d70346a" + integrity sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew== dependencies: - "@typescript-eslint/typescript-estree" "5.59.5" - "@typescript-eslint/utils" "5.59.5" + "@typescript-eslint/typescript-estree" "5.62.0" + "@typescript-eslint/utils" "5.62.0" debug "^4.3.4" tsutils "^3.21.0" -"@typescript-eslint/types@5.59.5": - version "5.59.5" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.59.5.tgz#e63c5952532306d97c6ea432cee0981f6d2258c7" - integrity sha512-xkfRPHbqSH4Ggx4eHRIO/eGL8XL4Ysb4woL8c87YuAo8Md7AUjyWKa9YMwTL519SyDPrfEgKdewjkxNCVeJW7w== +"@typescript-eslint/types@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.62.0.tgz#258607e60effa309f067608931c3df6fed41fd2f" + integrity sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ== -"@typescript-eslint/typescript-estree@5.59.5": - version "5.59.5" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.5.tgz#9b252ce55dd765e972a7a2f99233c439c5101e42" - integrity sha512-+XXdLN2CZLZcD/mO7mQtJMvCkzRfmODbeSKuMY/yXbGkzvA9rJyDY5qDYNoiz2kP/dmyAxXquL2BvLQLJFPQIg== +"@typescript-eslint/typescript-estree@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz#7d17794b77fabcac615d6a48fb143330d962eb9b" + integrity sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA== dependencies: - "@typescript-eslint/types" "5.59.5" - "@typescript-eslint/visitor-keys" "5.59.5" + "@typescript-eslint/types" "5.62.0" + "@typescript-eslint/visitor-keys" "5.62.0" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/utils@5.59.5": - version "5.59.5" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.59.5.tgz#15b3eb619bb223302e60413adb0accd29c32bcae" - integrity sha512-sCEHOiw+RbyTii9c3/qN74hYDPNORb8yWCoPLmB7BIflhplJ65u2PBpdRla12e3SSTJ2erRkPjz7ngLHhUegxA== +"@typescript-eslint/utils@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.62.0.tgz#141e809c71636e4a75daa39faed2fb5f4b10df86" + integrity sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ== dependencies: "@eslint-community/eslint-utils" "^4.2.0" "@types/json-schema" "^7.0.9" "@types/semver" "^7.3.12" - "@typescript-eslint/scope-manager" "5.59.5" - "@typescript-eslint/types" "5.59.5" - "@typescript-eslint/typescript-estree" "5.59.5" + "@typescript-eslint/scope-manager" "5.62.0" + "@typescript-eslint/types" "5.62.0" + "@typescript-eslint/typescript-estree" "5.62.0" eslint-scope "^5.1.1" semver "^7.3.7" -"@typescript-eslint/visitor-keys@5.59.5": - version "5.59.5" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.5.tgz#ba5b8d6791a13cf9fea6716af1e7626434b29b9b" - integrity sha512-qL+Oz+dbeBRTeyJTIy0eniD3uvqU7x+y1QceBismZ41hd4aBSRh8UAw4pZP0+XzLuPZmx4raNMq/I+59W2lXKA== +"@typescript-eslint/visitor-keys@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz#2174011917ce582875954ffe2f6912d5931e353e" + integrity sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw== dependencies: - "@typescript-eslint/types" "5.59.5" + "@typescript-eslint/types" "5.62.0" eslint-visitor-keys "^3.3.0" acorn-jsx@^5.3.2: @@ -845,12 +821,12 @@ acorn-jsx@^5.3.2: resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== -acorn@^8.8.0: - version "8.8.2" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.2.tgz#1b2f25db02af965399b9776b0c2c391276d37c4a" - integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw== +acorn@^8.9.0: + version "8.10.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5" + integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw== -ajv@^6.10.0, ajv@^6.12.4: +ajv@^6.12.4: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== @@ -921,15 +897,15 @@ asynckit@^0.4.0: resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== -babel-jest@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.5.0.tgz#3fe3ddb109198e78b1c88f9ebdecd5e4fc2f50a5" - integrity sha512-mA4eCDh5mSo2EcA9xQjVTpmbbNk32Zb3Q3QFQsNhaK56Q+yoXowzFodLux30HRgyOho5rsQ6B0P9QpMkvvnJ0Q== +babel-jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.7.0.tgz#f4369919225b684c56085998ac63dbd05be020d5" + integrity sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg== dependencies: - "@jest/transform" "^29.5.0" + "@jest/transform" "^29.7.0" "@types/babel__core" "^7.1.14" babel-plugin-istanbul "^6.1.1" - babel-preset-jest "^29.5.0" + babel-preset-jest "^29.6.3" chalk "^4.0.0" graceful-fs "^4.2.9" slash "^3.0.0" @@ -945,10 +921,10 @@ babel-plugin-istanbul@^6.1.1: istanbul-lib-instrument "^5.0.4" test-exclude "^6.0.0" -babel-plugin-jest-hoist@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.5.0.tgz#a97db437936f441ec196990c9738d4b88538618a" - integrity sha512-zSuuuAlTMT4mzLj2nPnUm6fsE6270vdOfnpbJ+RmruU75UhLFvL0N2NgI7xpeS7NaB6hGqmd5pVpGTDYvi4Q3w== +babel-plugin-jest-hoist@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz#aadbe943464182a8922c3c927c3067ff40d24626" + integrity sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg== dependencies: "@babel/template" "^7.3.3" "@babel/types" "^7.3.3" @@ -973,12 +949,12 @@ babel-preset-current-node-syntax@^1.0.0: "@babel/plugin-syntax-optional-chaining" "^7.8.3" "@babel/plugin-syntax-top-level-await" "^7.8.3" -babel-preset-jest@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.5.0.tgz#57bc8cc88097af7ff6a5ab59d1cd29d52a5916e2" - integrity sha512-JOMloxOqdiBSxMAzjRaH023/vvcaSaec49zvg+2LmNsktC7ei39LTJGw02J+9uUtTZUq6xbLyJ4dxe9sSmIuAg== +babel-preset-jest@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz#fa05fa510e7d493896d7b0dd2033601c840f171c" + integrity sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA== dependencies: - babel-plugin-jest-hoist "^29.5.0" + babel-plugin-jest-hoist "^29.6.3" babel-preset-current-node-syntax "^1.0.0" balanced-match@^1.0.0: @@ -1001,15 +977,15 @@ braces@^3.0.2: dependencies: fill-range "^7.0.1" -browserslist@^4.21.3: - version "4.21.5" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.5.tgz#75c5dae60063ee641f977e00edd3cfb2fb7af6a7" - integrity sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w== +browserslist@^4.21.9: + version "4.22.1" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.22.1.tgz#ba91958d1a59b87dab6fed8dfbcb3da5e2e9c619" + integrity sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ== dependencies: - caniuse-lite "^1.0.30001449" - electron-to-chromium "^1.4.284" - node-releases "^2.0.8" - update-browserslist-db "^1.0.10" + caniuse-lite "^1.0.30001541" + electron-to-chromium "^1.4.535" + node-releases "^2.0.13" + update-browserslist-db "^1.0.13" bs-logger@0.x: version "0.2.6" @@ -1045,12 +1021,12 @@ camelcase@^6.2.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== -caniuse-lite@^1.0.30001449: - version "1.0.30001486" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001486.tgz#56a08885228edf62cbe1ac8980f2b5dae159997e" - integrity sha512-uv7/gXuHi10Whlj0pp5q/tsK/32J2QSqVRKQhs2j8VsDCjgyruAh/eEXHF822VqO9yT6iZKw3nRwZRSPBE9OQg== +caniuse-lite@^1.0.30001541: + version "1.0.30001542" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001542.tgz#823ddb5aed0a70d5e2bfb49126478e84e9514b85" + integrity sha512-UrtAXVcj1mvPBFQ4sKd38daP8dEcXXr5sQe6QNNinaPd0iA/cxg9/l3VrSdL73jgw5sKyuQ6jNgiKO12W3SsVA== -chalk@^2.0.0: +chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -1078,9 +1054,9 @@ ci-info@^3.2.0: integrity sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw== cjs-module-lexer@^1.0.0: - version "1.2.2" - resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz#9f84ba3244a512f3a54e5277e8eef4c489864e40" - integrity sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA== + version "1.2.3" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz#6c370ab19f8a3394e318fe682686ec0ac684d107" + integrity sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ== cliui@^8.0.1: version "8.0.1" @@ -1097,9 +1073,9 @@ co@^4.6.0: integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== collect-v8-coverage@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz#cc2c8e94fc18bbdffe64d6534570c8a673b27f59" - integrity sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg== + version "1.0.2" + resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz#c0b29bcd33bcd0779a1344c2136051e6afd3d9e9" + integrity sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q== color-convert@^1.9.0: version "1.9.3" @@ -1137,7 +1113,7 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== -convert-source-map@^1.6.0, convert-source-map@^1.7.0: +convert-source-map@^1.6.0: version "1.9.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== @@ -1147,6 +1123,19 @@ convert-source-map@^2.0.0: resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== +create-jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/create-jest/-/create-jest-29.7.0.tgz#a355c5b3cb1e1af02ba177fe7afd7feee49a5320" + integrity sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q== + dependencies: + "@jest/types" "^29.6.3" + chalk "^4.0.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-config "^29.7.0" + jest-util "^29.7.0" + prompts "^2.0.1" + cross-fetch@^3.1.5: version "3.1.8" resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.8.tgz#0327eba65fd68a7d119f8fb2bf9334a1a7956f82" @@ -1170,10 +1159,10 @@ debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4: dependencies: ms "2.1.2" -dedent@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" - integrity sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA== +dedent@^1.0.0: + version "1.5.1" + resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.5.1.tgz#4f3fc94c8b711e9bb2800d185cd6ad20f2a90aff" + integrity sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg== deep-is@^0.1.3: version "0.1.4" @@ -1195,10 +1184,10 @@ detect-newline@^3.0.0: resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== -diff-sequences@^29.4.3: - version "29.4.3" - resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.4.3.tgz#9314bc1fabe09267ffeca9cbafc457d8499a13f2" - integrity sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA== +diff-sequences@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" + integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== dir-glob@^3.0.1: version "3.0.1" @@ -1214,10 +1203,10 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" -electron-to-chromium@^1.4.284: - version "1.4.389" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.389.tgz#ad70b94514f633ee2d12bdd8d7db5e8b13b7816a" - integrity sha512-WDgWUOK8ROR7sDFyYmxCUOoDc50lPgYAHAHwnnD1iN3SKO/mpqftb9iIPiEkMKmqYdkrR0j3N/O+YB/U7lSxwg== +electron-to-chromium@^1.4.535: + version "1.4.537" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.537.tgz#aac4101db53066be1e49baedd000a26bc754adc9" + integrity sha512-W1+g9qs9hviII0HAwOdehGYkr+zt7KKdmCcJcjH0mYg6oL8+ioT3Skjmt7BLoAQqXhjf40AXd+HlR4oAWMlXjA== emittery@^0.13.1: version "0.13.1" @@ -1271,40 +1260,40 @@ eslint-scope@^5.1.1: esrecurse "^4.3.0" estraverse "^4.1.1" -eslint-scope@^7.2.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.0.tgz#f21ebdafda02352f103634b96dd47d9f81ca117b" - integrity sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw== +eslint-scope@^7.2.2: + version "7.2.2" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" + integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== dependencies: esrecurse "^4.3.0" estraverse "^5.2.0" -eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1: - version "3.4.1" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz#c22c48f48942d08ca824cc526211ae400478a994" - integrity sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA== +eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" + integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== eslint@^8.40.0: - version "8.40.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.40.0.tgz#a564cd0099f38542c4e9a2f630fa45bf33bc42a4" - integrity sha512-bvR+TsP9EHL3TqNtj9sCNJVAFK3fBN8Q7g5waghxyRsPLIMwL73XSKnZFK0hk/O2ANC+iAoq6PWMQ+IfBAJIiQ== + version "8.50.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.50.0.tgz#2ae6015fee0240fcd3f83e1e25df0287f487d6b2" + integrity sha512-FOnOGSuFuFLv/Sa+FDVRZl4GGVAAFFi8LecRsI5a1tMO5HIE8nCm4ivAlzt4dT3ol/PaaGC0rJEEXQmHJBGoOg== dependencies: "@eslint-community/eslint-utils" "^4.2.0" - "@eslint-community/regexpp" "^4.4.0" - "@eslint/eslintrc" "^2.0.3" - "@eslint/js" "8.40.0" - "@humanwhocodes/config-array" "^0.11.8" + "@eslint-community/regexpp" "^4.6.1" + "@eslint/eslintrc" "^2.1.2" + "@eslint/js" "8.50.0" + "@humanwhocodes/config-array" "^0.11.11" "@humanwhocodes/module-importer" "^1.0.1" "@nodelib/fs.walk" "^1.2.8" - ajv "^6.10.0" + ajv "^6.12.4" chalk "^4.0.0" cross-spawn "^7.0.2" debug "^4.3.2" doctrine "^3.0.0" escape-string-regexp "^4.0.0" - eslint-scope "^7.2.0" - eslint-visitor-keys "^3.4.1" - espree "^9.5.2" + eslint-scope "^7.2.2" + eslint-visitor-keys "^3.4.3" + espree "^9.6.1" esquery "^1.4.2" esutils "^2.0.2" fast-deep-equal "^3.1.3" @@ -1312,30 +1301,27 @@ eslint@^8.40.0: find-up "^5.0.0" glob-parent "^6.0.2" globals "^13.19.0" - grapheme-splitter "^1.0.4" + graphemer "^1.4.0" ignore "^5.2.0" - import-fresh "^3.0.0" imurmurhash "^0.1.4" is-glob "^4.0.0" is-path-inside "^3.0.3" - js-sdsl "^4.1.4" js-yaml "^4.1.0" json-stable-stringify-without-jsonify "^1.0.1" levn "^0.4.1" lodash.merge "^4.6.2" minimatch "^3.1.2" natural-compare "^1.4.0" - optionator "^0.9.1" + optionator "^0.9.3" strip-ansi "^6.0.1" - strip-json-comments "^3.1.0" text-table "^0.2.0" -espree@^9.5.2: - version "9.5.2" - resolved "https://registry.yarnpkg.com/espree/-/espree-9.5.2.tgz#e994e7dc33a082a7a82dceaf12883a829353215b" - integrity sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw== +espree@^9.6.0, espree@^9.6.1: + version "9.6.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" + integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== dependencies: - acorn "^8.8.0" + acorn "^8.9.0" acorn-jsx "^5.3.2" eslint-visitor-keys "^3.4.1" @@ -1393,16 +1379,16 @@ exit@^0.1.2: resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== -expect@^29.0.0, expect@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/expect/-/expect-29.5.0.tgz#68c0509156cb2a0adb8865d413b137eeaae682f7" - integrity sha512-yM7xqUrCO2JdpFo4XpM82t+PJBFybdqoQuJLDGeDX2ij8NZzqRHyu3Hp188/JX7SWqud+7t4MUdvcgGBICMHZg== +expect@^29.0.0, expect@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/expect/-/expect-29.7.0.tgz#578874590dcb3214514084c08115d8aee61e11bc" + integrity sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw== dependencies: - "@jest/expect-utils" "^29.5.0" - jest-get-type "^29.4.3" - jest-matcher-utils "^29.5.0" - jest-message-util "^29.5.0" - jest-util "^29.5.0" + "@jest/expect-utils" "^29.7.0" + jest-get-type "^29.6.3" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" @@ -1410,14 +1396,14 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== fast-diff@^1.1.2: - version "1.2.0" - resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" - integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== + version "1.3.0" + resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0" + integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw== fast-glob@^3.2.9: - version "3.2.12" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.12.tgz#7f39ec99c2e6ab030337142da9e0c18f37afae80" - integrity sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w== + version "3.3.1" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.1.tgz#784b4e897340f3dbbef17413b3f11acf03c874c4" + integrity sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg== dependencies: "@nodelib/fs.stat" "^2.0.2" "@nodelib/fs.walk" "^1.2.3" @@ -1480,17 +1466,18 @@ find-up@^5.0.0: path-exists "^4.0.0" flat-cache@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" - integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== + version "3.1.0" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.1.0.tgz#0e54ab4a1a60fe87e2946b6b00657f1c99e1af3f" + integrity sha512-OHx4Qwrrt0E4jEIcI5/Xb+f+QmJYNj2rrK8wiIdQOIrB9WrrJL8cjZvXdXuBTkkEwEqLycb5BeZDV1o2i9bTew== dependencies: - flatted "^3.1.0" + flatted "^3.2.7" + keyv "^4.5.3" rimraf "^3.0.2" -flatted@^3.1.0: - version "3.2.7" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787" - integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== +flatted@^3.2.7: + version "3.2.9" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.9.tgz#7eb4c67ca1ba34232ca9d2d93e9886e611ad7daf" + integrity sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ== form-data@^4.0.0: version "4.0.0" @@ -1507,9 +1494,9 @@ fs.realpath@^1.0.0: integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== fsevents@^2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" - integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== function-bind@^1.1.1: version "1.1.1" @@ -1568,9 +1555,9 @@ globals@^11.1.0: integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== globals@^13.19.0: - version "13.20.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.20.0.tgz#ea276a1e508ffd4f1612888f9d1bad1e2717bf82" - integrity sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ== + version "13.22.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.22.0.tgz#0c9fcb9c48a2494fbb5edbfee644285543eba9d8" + integrity sha512-H1Ddc/PbZHTDVJSnj8kWptIRSD6AM3pK+mKytuIVF4uoBV7rshFlhhvA58ceJ5wp3Er58w6zj7bykMpYXt3ETw== dependencies: type-fest "^0.20.2" @@ -1591,10 +1578,10 @@ graceful-fs@^4.2.9: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== -grapheme-splitter@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e" - integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ== +graphemer@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" + integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== has-flag@^3.0.0: version "3.0.0" @@ -1628,7 +1615,7 @@ ignore@^5.2.0: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324" integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ== -import-fresh@^3.0.0, import-fresh@^3.2.1: +import-fresh@^3.2.1: version "3.3.0" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== @@ -1667,10 +1654,10 @@ is-arrayish@^0.2.1: resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== -is-core-module@^2.11.0: - version "2.12.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.12.0.tgz#36ad62f6f73c8253fd6472517a12483cf03e7ec4" - integrity sha512-RECHCBCd/viahWmwj6enj19sKbHfJrddi/6cBDsNTKbNq0f7VeaUkBo60BqzvPqo/W54ChS62Z5qyun7cfOMqQ== +is-core-module@^2.13.0: + version "2.13.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.0.tgz#bb52aa6e2cbd49a30c2ba68c42bf3435ba6072db" + integrity sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ== dependencies: has "^1.0.3" @@ -1721,7 +1708,7 @@ istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3" integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw== -istanbul-lib-instrument@^5.0.4, istanbul-lib-instrument@^5.1.0: +istanbul-lib-instrument@^5.0.4: version "5.2.1" resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz#d10c8885c2125574e1c231cacadf955675e1ce3d" integrity sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg== @@ -1732,13 +1719,24 @@ istanbul-lib-instrument@^5.0.4, istanbul-lib-instrument@^5.1.0: istanbul-lib-coverage "^3.2.0" semver "^6.3.0" +istanbul-lib-instrument@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.0.tgz#7a8af094cbfff1d5bb280f62ce043695ae8dd5b8" + integrity sha512-x58orMzEVfzPUKqlbLd1hXCnySCxKdDKa6Rjg97CwuLLRI4g3FHTdnExu1OqffVFay6zeMW+T6/DowFLndWnIw== + dependencies: + "@babel/core" "^7.12.3" + "@babel/parser" "^7.14.7" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-coverage "^3.2.0" + semver "^7.5.4" + istanbul-lib-report@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#7518fe52ea44de372f460a76b5ecda9ffb73d8a6" - integrity sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw== + version "3.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#908305bac9a5bd175ac6a74489eafd0fc2445a7d" + integrity sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw== dependencies: istanbul-lib-coverage "^3.0.0" - make-dir "^3.0.0" + make-dir "^4.0.0" supports-color "^7.1.0" istanbul-lib-source-maps@^4.0.0: @@ -1751,378 +1749,370 @@ istanbul-lib-source-maps@^4.0.0: source-map "^0.6.1" istanbul-reports@^3.1.3: - version "3.1.5" - resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.5.tgz#cc9a6ab25cb25659810e4785ed9d9fb742578bae" - integrity sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w== + version "3.1.6" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.6.tgz#2544bcab4768154281a2f0870471902704ccaa1a" + integrity sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg== dependencies: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" -jest-changed-files@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.5.0.tgz#e88786dca8bf2aa899ec4af7644e16d9dcf9b23e" - integrity sha512-IFG34IUMUaNBIxjQXF/iu7g6EcdMrGRRxaUSw92I/2g2YC6vCdTltl4nHvt7Ci5nSJwXIkCu8Ka1DKF+X7Z1Ag== +jest-changed-files@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.7.0.tgz#1c06d07e77c78e1585d020424dedc10d6e17ac3a" + integrity sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w== dependencies: execa "^5.0.0" + jest-util "^29.7.0" p-limit "^3.1.0" -jest-circus@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.5.0.tgz#b5926989449e75bff0d59944bae083c9d7fb7317" - integrity sha512-gq/ongqeQKAplVxqJmbeUOJJKkW3dDNPY8PjhJ5G0lBRvu0e3EWGxGy5cI4LAGA7gV2UHCtWBI4EMXK8c9nQKA== +jest-circus@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.7.0.tgz#b6817a45fcc835d8b16d5962d0c026473ee3668a" + integrity sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw== dependencies: - "@jest/environment" "^29.5.0" - "@jest/expect" "^29.5.0" - "@jest/test-result" "^29.5.0" - "@jest/types" "^29.5.0" + "@jest/environment" "^29.7.0" + "@jest/expect" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" "@types/node" "*" chalk "^4.0.0" co "^4.6.0" - dedent "^0.7.0" + dedent "^1.0.0" is-generator-fn "^2.0.0" - jest-each "^29.5.0" - jest-matcher-utils "^29.5.0" - jest-message-util "^29.5.0" - jest-runtime "^29.5.0" - jest-snapshot "^29.5.0" - jest-util "^29.5.0" + jest-each "^29.7.0" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-runtime "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" p-limit "^3.1.0" - pretty-format "^29.5.0" + pretty-format "^29.7.0" pure-rand "^6.0.0" slash "^3.0.0" stack-utils "^2.0.3" -jest-cli@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.5.0.tgz#b34c20a6d35968f3ee47a7437ff8e53e086b4a67" - integrity sha512-L1KcP1l4HtfwdxXNFCL5bmUbLQiKrakMUriBEcc1Vfz6gx31ORKdreuWvmQVBit+1ss9NNR3yxjwfwzZNdQXJw== +jest-cli@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.7.0.tgz#5592c940798e0cae677eec169264f2d839a37995" + integrity sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg== dependencies: - "@jest/core" "^29.5.0" - "@jest/test-result" "^29.5.0" - "@jest/types" "^29.5.0" + "@jest/core" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" chalk "^4.0.0" + create-jest "^29.7.0" exit "^0.1.2" - graceful-fs "^4.2.9" import-local "^3.0.2" - jest-config "^29.5.0" - jest-util "^29.5.0" - jest-validate "^29.5.0" - prompts "^2.0.1" + jest-config "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" yargs "^17.3.1" -jest-config@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.5.0.tgz#3cc972faec8c8aaea9ae158c694541b79f3748da" - integrity sha512-kvDUKBnNJPNBmFFOhDbm59iu1Fii1Q6SxyhXfvylq3UTHbg6o7j/g8k2dZyXWLvfdKB1vAPxNZnMgtKJcmu3kA== +jest-config@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.7.0.tgz#bcbda8806dbcc01b1e316a46bb74085a84b0245f" + integrity sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ== dependencies: "@babel/core" "^7.11.6" - "@jest/test-sequencer" "^29.5.0" - "@jest/types" "^29.5.0" - babel-jest "^29.5.0" + "@jest/test-sequencer" "^29.7.0" + "@jest/types" "^29.6.3" + babel-jest "^29.7.0" chalk "^4.0.0" ci-info "^3.2.0" deepmerge "^4.2.2" glob "^7.1.3" graceful-fs "^4.2.9" - jest-circus "^29.5.0" - jest-environment-node "^29.5.0" - jest-get-type "^29.4.3" - jest-regex-util "^29.4.3" - jest-resolve "^29.5.0" - jest-runner "^29.5.0" - jest-util "^29.5.0" - jest-validate "^29.5.0" + jest-circus "^29.7.0" + jest-environment-node "^29.7.0" + jest-get-type "^29.6.3" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-runner "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" micromatch "^4.0.4" parse-json "^5.2.0" - pretty-format "^29.5.0" + pretty-format "^29.7.0" slash "^3.0.0" strip-json-comments "^3.1.1" -jest-diff@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.5.0.tgz#e0d83a58eb5451dcc1fa61b1c3ee4e8f5a290d63" - integrity sha512-LtxijLLZBduXnHSniy0WMdaHjmQnt3g5sa16W4p0HqukYTTsyTW3GD1q41TyGl5YFXj/5B2U6dlh5FM1LIMgxw== +jest-diff@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.7.0.tgz#017934a66ebb7ecf6f205e84699be10afd70458a" + integrity sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw== dependencies: chalk "^4.0.0" - diff-sequences "^29.4.3" - jest-get-type "^29.4.3" - pretty-format "^29.5.0" + diff-sequences "^29.6.3" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" -jest-docblock@^29.4.3: - version "29.4.3" - resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.4.3.tgz#90505aa89514a1c7dceeac1123df79e414636ea8" - integrity sha512-fzdTftThczeSD9nZ3fzA/4KkHtnmllawWrXO69vtI+L9WjEIuXWs4AmyME7lN5hU7dB0sHhuPfcKofRsUb/2Fg== +jest-docblock@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.7.0.tgz#8fddb6adc3cdc955c93e2a87f61cfd350d5d119a" + integrity sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g== dependencies: detect-newline "^3.0.0" -jest-each@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.5.0.tgz#fc6e7014f83eac68e22b7195598de8554c2e5c06" - integrity sha512-HM5kIJ1BTnVt+DQZ2ALp3rzXEl+g726csObrW/jpEGl+CDSSQpOJJX2KE/vEg8cxcMXdyEPu6U4QX5eruQv5hA== +jest-each@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.7.0.tgz#162a9b3f2328bdd991beaabffbb74745e56577d1" + integrity sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ== dependencies: - "@jest/types" "^29.5.0" + "@jest/types" "^29.6.3" chalk "^4.0.0" - jest-get-type "^29.4.3" - jest-util "^29.5.0" - pretty-format "^29.5.0" + jest-get-type "^29.6.3" + jest-util "^29.7.0" + pretty-format "^29.7.0" -jest-environment-node@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.5.0.tgz#f17219d0f0cc0e68e0727c58b792c040e332c967" - integrity sha512-ExxuIK/+yQ+6PRGaHkKewYtg6hto2uGCgvKdb2nfJfKXgZ17DfXjvbZ+jA1Qt9A8EQSfPnt5FKIfnOO3u1h9qw== +jest-environment-node@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.7.0.tgz#0b93e111dda8ec120bc8300e6d1fb9576e164376" + integrity sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw== dependencies: - "@jest/environment" "^29.5.0" - "@jest/fake-timers" "^29.5.0" - "@jest/types" "^29.5.0" + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" "@types/node" "*" - jest-mock "^29.5.0" - jest-util "^29.5.0" + jest-mock "^29.7.0" + jest-util "^29.7.0" -jest-get-type@^29.4.3: - version "29.4.3" - resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.4.3.tgz#1ab7a5207c995161100b5187159ca82dd48b3dd5" - integrity sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg== +jest-get-type@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1" + integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw== -jest-haste-map@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.5.0.tgz#69bd67dc9012d6e2723f20a945099e972b2e94de" - integrity sha512-IspOPnnBro8YfVYSw6yDRKh/TiCdRngjxeacCps1cQ9cgVN6+10JUcuJ1EabrgYLOATsIAigxA0rLR9x/YlrSA== +jest-haste-map@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.7.0.tgz#3c2396524482f5a0506376e6c858c3bbcc17b104" + integrity sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA== dependencies: - "@jest/types" "^29.5.0" + "@jest/types" "^29.6.3" "@types/graceful-fs" "^4.1.3" "@types/node" "*" anymatch "^3.0.3" fb-watchman "^2.0.0" graceful-fs "^4.2.9" - jest-regex-util "^29.4.3" - jest-util "^29.5.0" - jest-worker "^29.5.0" + jest-regex-util "^29.6.3" + jest-util "^29.7.0" + jest-worker "^29.7.0" micromatch "^4.0.4" walker "^1.0.8" optionalDependencies: fsevents "^2.3.2" -jest-leak-detector@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.5.0.tgz#cf4bdea9615c72bac4a3a7ba7e7930f9c0610c8c" - integrity sha512-u9YdeeVnghBUtpN5mVxjID7KbkKE1QU4f6uUwuxiY0vYRi9BUCLKlPEZfDGR67ofdFmDz9oPAy2G92Ujrntmow== +jest-leak-detector@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz#5b7ec0dadfdfec0ca383dc9aa016d36b5ea4c728" + integrity sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw== dependencies: - jest-get-type "^29.4.3" - pretty-format "^29.5.0" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" -jest-matcher-utils@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.5.0.tgz#d957af7f8c0692c5453666705621ad4abc2c59c5" - integrity sha512-lecRtgm/rjIK0CQ7LPQwzCs2VwW6WAahA55YBuI+xqmhm7LAaxokSB8C97yJeYyT+HvQkH741StzpU41wohhWw== +jest-matcher-utils@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz#ae8fec79ff249fd592ce80e3ee474e83a6c44f12" + integrity sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g== dependencies: chalk "^4.0.0" - jest-diff "^29.5.0" - jest-get-type "^29.4.3" - pretty-format "^29.5.0" + jest-diff "^29.7.0" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" -jest-message-util@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.5.0.tgz#1f776cac3aca332ab8dd2e3b41625435085c900e" - integrity sha512-Kijeg9Dag6CKtIDA7O21zNTACqD5MD/8HfIV8pdD94vFyFuer52SigdC3IQMhab3vACxXMiFk+yMHNdbqtyTGA== +jest-message-util@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.7.0.tgz#8bc392e204e95dfe7564abbe72a404e28e51f7f3" + integrity sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w== dependencies: "@babel/code-frame" "^7.12.13" - "@jest/types" "^29.5.0" + "@jest/types" "^29.6.3" "@types/stack-utils" "^2.0.0" chalk "^4.0.0" graceful-fs "^4.2.9" micromatch "^4.0.4" - pretty-format "^29.5.0" + pretty-format "^29.7.0" slash "^3.0.0" stack-utils "^2.0.3" -jest-mock@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.5.0.tgz#26e2172bcc71d8b0195081ff1f146ac7e1518aed" - integrity sha512-GqOzvdWDE4fAV2bWQLQCkujxYWL7RxjCnj71b5VhDAGOevB3qj3Ovg26A5NI84ZpODxyzaozXLOh2NCgkbvyaw== +jest-mock@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.7.0.tgz#4e836cf60e99c6fcfabe9f99d017f3fdd50a6347" + integrity sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw== dependencies: - "@jest/types" "^29.5.0" + "@jest/types" "^29.6.3" "@types/node" "*" - jest-util "^29.5.0" + jest-util "^29.7.0" jest-pnp-resolver@^1.2.2: version "1.2.3" resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz#930b1546164d4ad5937d5540e711d4d38d4cad2e" integrity sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w== -jest-regex-util@^29.4.3: - version "29.4.3" - resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.4.3.tgz#a42616141e0cae052cfa32c169945d00c0aa0bb8" - integrity sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg== +jest-regex-util@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.6.3.tgz#4a556d9c776af68e1c5f48194f4d0327d24e8a52" + integrity sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg== -jest-resolve-dependencies@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.5.0.tgz#f0ea29955996f49788bf70996052aa98e7befee4" - integrity sha512-sjV3GFr0hDJMBpYeUuGduP+YeCRbd7S/ck6IvL3kQ9cpySYKqcqhdLLC2rFwrcL7tz5vYibomBrsFYWkIGGjOg== +jest-resolve-dependencies@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz#1b04f2c095f37fc776ff40803dc92921b1e88428" + integrity sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA== dependencies: - jest-regex-util "^29.4.3" - jest-snapshot "^29.5.0" + jest-regex-util "^29.6.3" + jest-snapshot "^29.7.0" -jest-resolve@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.5.0.tgz#b053cc95ad1d5f6327f0ac8aae9f98795475ecdc" - integrity sha512-1TzxJ37FQq7J10jPtQjcc+MkCkE3GBpBecsSUWJ0qZNJpmg6m0D9/7II03yJulm3H/fvVjgqLh/k2eYg+ui52w== +jest-resolve@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.7.0.tgz#64d6a8992dd26f635ab0c01e5eef4399c6bcbc30" + integrity sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA== dependencies: chalk "^4.0.0" graceful-fs "^4.2.9" - jest-haste-map "^29.5.0" + jest-haste-map "^29.7.0" jest-pnp-resolver "^1.2.2" - jest-util "^29.5.0" - jest-validate "^29.5.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" resolve "^1.20.0" resolve.exports "^2.0.0" slash "^3.0.0" -jest-runner@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.5.0.tgz#6a57c282eb0ef749778d444c1d758c6a7693b6f8" - integrity sha512-m7b6ypERhFghJsslMLhydaXBiLf7+jXy8FwGRHO3BGV1mcQpPbwiqiKUR2zU2NJuNeMenJmlFZCsIqzJCTeGLQ== +jest-runner@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.7.0.tgz#809af072d408a53dcfd2e849a4c976d3132f718e" + integrity sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ== dependencies: - "@jest/console" "^29.5.0" - "@jest/environment" "^29.5.0" - "@jest/test-result" "^29.5.0" - "@jest/transform" "^29.5.0" - "@jest/types" "^29.5.0" + "@jest/console" "^29.7.0" + "@jest/environment" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" "@types/node" "*" chalk "^4.0.0" emittery "^0.13.1" graceful-fs "^4.2.9" - jest-docblock "^29.4.3" - jest-environment-node "^29.5.0" - jest-haste-map "^29.5.0" - jest-leak-detector "^29.5.0" - jest-message-util "^29.5.0" - jest-resolve "^29.5.0" - jest-runtime "^29.5.0" - jest-util "^29.5.0" - jest-watcher "^29.5.0" - jest-worker "^29.5.0" + jest-docblock "^29.7.0" + jest-environment-node "^29.7.0" + jest-haste-map "^29.7.0" + jest-leak-detector "^29.7.0" + jest-message-util "^29.7.0" + jest-resolve "^29.7.0" + jest-runtime "^29.7.0" + jest-util "^29.7.0" + jest-watcher "^29.7.0" + jest-worker "^29.7.0" p-limit "^3.1.0" source-map-support "0.5.13" -jest-runtime@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.5.0.tgz#c83f943ee0c1da7eb91fa181b0811ebd59b03420" - integrity sha512-1Hr6Hh7bAgXQP+pln3homOiEZtCDZFqwmle7Ew2j8OlbkIu6uE3Y/etJQG8MLQs3Zy90xrp2C0BRrtPHG4zryw== +jest-runtime@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.7.0.tgz#efecb3141cf7d3767a3a0cc8f7c9990587d3d817" + integrity sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ== dependencies: - "@jest/environment" "^29.5.0" - "@jest/fake-timers" "^29.5.0" - "@jest/globals" "^29.5.0" - "@jest/source-map" "^29.4.3" - "@jest/test-result" "^29.5.0" - "@jest/transform" "^29.5.0" - "@jest/types" "^29.5.0" + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/globals" "^29.7.0" + "@jest/source-map" "^29.6.3" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" "@types/node" "*" chalk "^4.0.0" cjs-module-lexer "^1.0.0" collect-v8-coverage "^1.0.0" glob "^7.1.3" graceful-fs "^4.2.9" - jest-haste-map "^29.5.0" - jest-message-util "^29.5.0" - jest-mock "^29.5.0" - jest-regex-util "^29.4.3" - jest-resolve "^29.5.0" - jest-snapshot "^29.5.0" - jest-util "^29.5.0" + jest-haste-map "^29.7.0" + jest-message-util "^29.7.0" + jest-mock "^29.7.0" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" slash "^3.0.0" strip-bom "^4.0.0" -jest-snapshot@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.5.0.tgz#c9c1ce0331e5b63cd444e2f95a55a73b84b1e8ce" - integrity sha512-x7Wolra5V0tt3wRs3/ts3S6ciSQVypgGQlJpz2rsdQYoUKxMxPNaoHMGJN6qAuPJqS+2iQ1ZUn5kl7HCyls84g== +jest-snapshot@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.7.0.tgz#c2c574c3f51865da1bb329036778a69bf88a6be5" + integrity sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw== dependencies: "@babel/core" "^7.11.6" "@babel/generator" "^7.7.2" "@babel/plugin-syntax-jsx" "^7.7.2" "@babel/plugin-syntax-typescript" "^7.7.2" - "@babel/traverse" "^7.7.2" "@babel/types" "^7.3.3" - "@jest/expect-utils" "^29.5.0" - "@jest/transform" "^29.5.0" - "@jest/types" "^29.5.0" - "@types/babel__traverse" "^7.0.6" - "@types/prettier" "^2.1.5" + "@jest/expect-utils" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" babel-preset-current-node-syntax "^1.0.0" chalk "^4.0.0" - expect "^29.5.0" + expect "^29.7.0" graceful-fs "^4.2.9" - jest-diff "^29.5.0" - jest-get-type "^29.4.3" - jest-matcher-utils "^29.5.0" - jest-message-util "^29.5.0" - jest-util "^29.5.0" + jest-diff "^29.7.0" + jest-get-type "^29.6.3" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" natural-compare "^1.4.0" - pretty-format "^29.5.0" - semver "^7.3.5" + pretty-format "^29.7.0" + semver "^7.5.3" -jest-util@^29.0.0, jest-util@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.5.0.tgz#24a4d3d92fc39ce90425311b23c27a6e0ef16b8f" - integrity sha512-RYMgG/MTadOr5t8KdhejfvUU82MxsCu5MF6KuDUHl+NuwzUt+Sm6jJWxTJVrDR1j5M/gJVCPKQEpWXY+yIQ6lQ== +jest-util@^29.0.0, jest-util@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc" + integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA== dependencies: - "@jest/types" "^29.5.0" + "@jest/types" "^29.6.3" "@types/node" "*" chalk "^4.0.0" ci-info "^3.2.0" graceful-fs "^4.2.9" picomatch "^2.2.3" -jest-validate@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.5.0.tgz#8e5a8f36178d40e47138dc00866a5f3bd9916ffc" - integrity sha512-pC26etNIi+y3HV8A+tUGr/lph9B18GnzSRAkPaaZJIE1eFdiYm6/CewuiJQ8/RlfHd1u/8Ioi8/sJ+CmbA+zAQ== +jest-validate@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.7.0.tgz#7bf705511c64da591d46b15fce41400d52147d9c" + integrity sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw== dependencies: - "@jest/types" "^29.5.0" + "@jest/types" "^29.6.3" camelcase "^6.2.0" chalk "^4.0.0" - jest-get-type "^29.4.3" + jest-get-type "^29.6.3" leven "^3.1.0" - pretty-format "^29.5.0" + pretty-format "^29.7.0" -jest-watcher@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.5.0.tgz#cf7f0f949828ba65ddbbb45c743a382a4d911363" - integrity sha512-KmTojKcapuqYrKDpRwfqcQ3zjMlwu27SYext9pt4GlF5FUgB+7XE1mcCnSm6a4uUpFyQIkb6ZhzZvHl+jiBCiA== +jest-watcher@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.7.0.tgz#7810d30d619c3a62093223ce6bb359ca1b28a2f2" + integrity sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g== dependencies: - "@jest/test-result" "^29.5.0" - "@jest/types" "^29.5.0" + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" "@types/node" "*" ansi-escapes "^4.2.1" chalk "^4.0.0" emittery "^0.13.1" - jest-util "^29.5.0" + jest-util "^29.7.0" string-length "^4.0.1" -jest-worker@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.5.0.tgz#bdaefb06811bd3384d93f009755014d8acb4615d" - integrity sha512-NcrQnevGoSp4b5kg+akIpthoAFHxPBcb5P6mYPY0fUNT+sSvmtu6jlkEle3anczUKIKEbMxFimk9oTP/tpIPgA== +jest-worker@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.7.0.tgz#acad073acbbaeb7262bd5389e1bcf43e10058d4a" + integrity sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw== dependencies: "@types/node" "*" - jest-util "^29.5.0" + jest-util "^29.7.0" merge-stream "^2.0.0" supports-color "^8.0.0" jest@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest/-/jest-29.5.0.tgz#f75157622f5ce7ad53028f2f8888ab53e1f1f24e" - integrity sha512-juMg3he2uru1QoXX078zTa7pO85QyB9xajZc6bU+d9yEGwrKX6+vGmJQ3UdVZsvTEUARIdObzH68QItim6OSSQ== + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest/-/jest-29.7.0.tgz#994676fc24177f088f1c5e3737f5697204ff2613" + integrity sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw== dependencies: - "@jest/core" "^29.5.0" - "@jest/types" "^29.5.0" + "@jest/core" "^29.7.0" + "@jest/types" "^29.6.3" import-local "^3.0.2" - jest-cli "^29.5.0" - -js-sdsl@^4.1.4: - version "4.4.0" - resolved "https://registry.yarnpkg.com/js-sdsl/-/js-sdsl-4.4.0.tgz#8b437dbe642daa95760400b602378ed8ffea8430" - integrity sha512-FfVSdx6pJ41Oa+CF7RDaFmTnCaFhua+SNYQX74riGOpl96x+2jQCqEfQ2bnXu/5DPCqlRuiqyvTJM0Qjz26IVg== + jest-cli "^29.7.0" js-tokens@^4.0.0: version "4.0.0" @@ -2149,6 +2139,11 @@ jsesc@^2.5.1: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + json-parse-even-better-errors@^2.3.0: version "2.3.1" resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" @@ -2164,11 +2159,18 @@ json-stable-stringify-without-jsonify@^1.0.1: resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== -json5@^2.2.2, json5@^2.2.3: +json5@^2.2.3: version "2.2.3" resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== +keyv@^4.5.3: + version "4.5.3" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.3.tgz#00873d2b046df737963157bd04f294ca818c9c25" + integrity sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug== + dependencies: + json-buffer "3.0.1" + kleur@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" @@ -2238,12 +2240,12 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" -make-dir@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" - integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== +make-dir@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e" + integrity sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw== dependencies: - semver "^6.0.0" + semver "^7.5.3" make-error@1.x: version "1.3.6" @@ -2315,9 +2317,9 @@ natural-compare@^1.4.0: integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== node-fetch@^2.6.12: - version "2.6.12" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.12.tgz#02eb8e22074018e3d5a83016649d04df0e348fba" - integrity sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g== + version "2.7.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" + integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== dependencies: whatwg-url "^5.0.0" @@ -2326,7 +2328,7 @@ node-int64@^0.4.0: resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== -node-releases@^2.0.8: +node-releases@^2.0.13: version "2.0.13" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.13.tgz#d5ed1627c23e3461e819b02e57b75e4899b1c81d" integrity sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ== @@ -2357,7 +2359,7 @@ onetime@^5.1.2: dependencies: mimic-fn "^2.1.0" -optionator@^0.9.1: +optionator@^0.9.3: version "0.9.3" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.3.tgz#007397d44ed1872fdc6ed31360190f81814e2c64" integrity sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg== @@ -2479,16 +2481,16 @@ prettier-linter-helpers@^1.0.0: fast-diff "^1.1.2" prettier@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.0.0.tgz#e7b19f691245a21d618c68bc54dc06122f6105ae" - integrity sha512-zBf5eHpwHOGPC47h0zrPyNn+eAEIdEzfywMoYn2XPi0P44Zp0tSq64rq0xAREh4auw2cJZHo9QUob+NqCQky4g== + version "3.0.3" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.0.3.tgz#432a51f7ba422d1469096c0fdc28e235db8f9643" + integrity sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg== -pretty-format@^29.0.0, pretty-format@^29.5.0: - version "29.6.1" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.6.1.tgz#ec838c288850b7c4f9090b867c2d4f4edbfb0f3e" - integrity sha512-7jRj+yXO0W7e4/tSJKoR7HRIHLPPjtNaUGG2xxKQnGvPNRkgWcQ0AZX6P4KBRJN4FcTBWb3sa7DVUJmocYuoog== +pretty-format@^29.0.0, pretty-format@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" + integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ== dependencies: - "@jest/schemas" "^29.6.0" + "@jest/schemas" "^29.6.3" ansi-styles "^5.0.0" react-is "^18.0.0" @@ -2506,9 +2508,9 @@ punycode@^2.1.0: integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA== pure-rand@^6.0.0: - version "6.0.2" - resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.0.2.tgz#a9c2ddcae9b68d736a8163036f088a2781c8b306" - integrity sha512-6Yg0ekpKICSjPswYOuC5sku/TSWaRYlA0qsXqJgM/d/4pLPHPuTxK7Nbf7jFKzAeedUhR8C7K9Uv63FBsSo8xQ== + version "6.0.4" + resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.0.4.tgz#50b737f6a925468679bff00ad20eade53f37d5c7" + integrity sha512-LA0Y9kxMYv47GIPJy6MI84fqTd2HmYZI83W/kM/SkKfDlajnZYfmXFTxkbY+xSBPkLJxltMa9hIkmdc29eguMA== queue-microtask@^1.2.2: version "1.2.3" @@ -2548,11 +2550,11 @@ resolve.exports@^2.0.0: integrity sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg== resolve@^1.20.0: - version "1.22.2" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.2.tgz#0ed0943d4e301867955766c9f3e1ae6d01c6845f" - integrity sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g== + version "1.22.6" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.6.tgz#dd209739eca3aef739c626fea1b4f3c506195362" + integrity sha512-njhxM7mV12JfufShqGy3Rz8j11RPdLy4xi15UurGJeoHLfJpVXKdh3ueuOqbYUcDZnffr6X739JBo5LzyahEsw== dependencies: - is-core-module "^2.11.0" + is-core-module "^2.13.0" path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" @@ -2575,12 +2577,12 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" -semver@^6.0.0, semver@^6.3.0: +semver@^6.3.0, semver@^6.3.1: version "6.3.1" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.3.5, semver@^7.3.7, semver@^7.5.3: +semver@^7.3.7, semver@^7.5.3, semver@^7.5.4: version "7.5.4" resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== @@ -2673,7 +2675,7 @@ strip-final-newline@^2.0.0: resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== -strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: +strip-json-comments@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== @@ -2789,14 +2791,14 @@ type-fest@^0.21.3: integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== typescript@^5.0.4: - version "5.1.6" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.1.6.tgz#02f8ac202b6dad2c0dd5e0913745b47a37998274" - integrity sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA== + version "5.2.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.2.2.tgz#5ebb5e5a5b75f085f22bc3f8460fba308310fa78" + integrity sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w== -update-browserslist-db@^1.0.10: - version "1.0.11" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz#9a2a641ad2907ae7b3616506f4b977851db5b940" - integrity sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA== +update-browserslist-db@^1.0.13: + version "1.0.13" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz#3c5e4f5c083661bd38ef64b6328c26ed6c8248c4" + integrity sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg== dependencies: escalade "^3.1.1" picocolors "^1.0.0" diff --git a/crates/apub/src/activities/community/announce.rs b/crates/apub/src/activities/community/announce.rs index e84a970f3..c704ad012 100644 --- a/crates/apub/src/activities/community/announce.rs +++ b/crates/apub/src/activities/community/announce.rs @@ -22,8 +22,8 @@ use activitypub_federation::{ traits::{ActivityHandler, Actor}, }; use lemmy_api_common::context::LemmyContext; -use lemmy_db_schema::source::activity::ActivitySendTargets; -use lemmy_utils::error::{LemmyError, LemmyErrorType}; +use lemmy_db_schema::source::{activity::ActivitySendTargets, community::CommunityFollower}; +use lemmy_utils::error::{LemmyError, LemmyErrorType, LemmyResult}; use serde_json::Value; use url::Url; @@ -46,24 +46,28 @@ impl ActivityHandler for RawAnnouncableActivities { } #[tracing::instrument(skip_all)] - async fn receive(self, data: &Data) -> Result<(), Self::Error> { + async fn receive(self, context: &Data) -> Result<(), Self::Error> { let activity: AnnouncableActivities = self.clone().try_into()?; + // This is only for sending, not receiving so we reject it. if let AnnouncableActivities::Page(_) = activity { Err(LemmyErrorType::CannotReceivePage)? } - // verify and receive activity - activity.verify(data).await?; - activity.clone().receive(data).await?; + // Need to treat community as optional here because `Delete/PrivateMessage` gets routed through + let community = activity.community(context).await.ok(); + can_accept_activity_in_community(&community, context).await?; - // if activity is in a community, send to followers - let community = activity.community(data).await; - if let Ok(community) = community { + // verify and receive activity + activity.verify(context).await?; + activity.clone().receive(context).await?; + + // if community is local, send activity to followers + if let Some(community) = community { if community.local { let actor_id = activity.actor().clone().into(); - verify_person_in_community(&actor_id, &community, data).await?; - AnnounceActivity::send(self, &community, data).await?; + verify_person_in_community(&actor_id, &community, context).await?; + AnnounceActivity::send(self, &community, context).await?; } } Ok(()) @@ -150,11 +154,15 @@ impl ActivityHandler for AnnounceActivity { #[tracing::instrument(skip_all)] async fn receive(self, context: &Data) -> Result<(), LemmyError> { let object: AnnouncableActivities = self.object.object(context).await?.try_into()?; + // This is only for sending, not receiving so we reject it. if let AnnouncableActivities::Page(_) = object { Err(LemmyErrorType::CannotReceivePage)? } + let community = object.community(context).await?; + can_accept_activity_in_community(&Some(community), context).await?; + // verify here in order to avoid fetching the object twice over http object.verify(context).await?; object.receive(context).await @@ -185,3 +193,23 @@ impl TryFrom for RawAnnouncableActivities { serde_json::from_value(serde_json::to_value(value)?) } } + +/// Check if an activity in the given community can be accepted. To return true, the community must +/// either be local to this instance, or it must have at least one local follower. +/// +/// TODO: This means mentions dont work if the community has no local followers. Can be fixed +/// by checking if any local user is in to/cc fields of activity. Anyway this is a minor +/// problem compared to receiving unsolicited posts. +async fn can_accept_activity_in_community( + community: &Option, + context: &Data, +) -> LemmyResult<()> { + if let Some(community) = community { + if !community.local + && !CommunityFollower::has_local_followers(&mut context.pool(), community.id).await? + { + Err(LemmyErrorType::CommunityHasNoFollowers)? + } + } + Ok(()) +} diff --git a/crates/apub/src/api/user_settings_backup.rs b/crates/apub/src/api/user_settings_backup.rs index aa09e51dc..d48260515 100644 --- a/crates/apub/src/api/user_settings_backup.rs +++ b/crates/apub/src/api/user_settings_backup.rs @@ -20,7 +20,6 @@ use lemmy_db_schema::{ post::{PostSaved, PostSavedForm}, }, traits::{Blockable, Crud, Followable, Saveable}, - utils::diesel_option_overwrite, }; use lemmy_db_views::structs::LocalUserView; use lemmy_utils::{ @@ -97,12 +96,9 @@ pub async fn import_settings( local_user_view: LocalUserView, context: Data, ) -> Result, LemmyError> { - let display_name = diesel_option_overwrite(data.display_name.clone()); - let bio = diesel_option_overwrite(data.bio.clone()); - let person_form = PersonUpdateForm { - display_name, - bio, + display_name: Some(data.display_name.clone()), + bio: Some(data.bio.clone()), matrix_user_id: Some(data.matrix_id.clone()), bot_account: data.bot_account, ..Default::default() diff --git a/crates/db_schema/src/impls/community.rs b/crates/db_schema/src/impls/community.rs index 82d2847f4..442239289 100644 --- a/crates/db_schema/src/impls/community.rs +++ b/crates/db_schema/src/impls/community.rs @@ -229,6 +229,22 @@ impl CommunityFollower { pub fn select_subscribed_type() -> dsl::Nullable { community_follower::pending.nullable() } + + /// Check if a remote instance has any followers on local instance. For this it is enough to check + /// if any follow relation is stored. Dont use this for local community. + pub async fn has_local_followers( + pool: &mut DbPool<'_>, + remote_community_id: CommunityId, + ) -> Result { + use crate::schema::community_follower::dsl::{community_follower, community_id}; + use diesel::dsl::{exists, select}; + let conn = &mut get_conn(pool).await?; + select(exists( + community_follower.filter(community_id.eq(remote_community_id)), + )) + .get_result(conn) + .await + } } impl Queryable, Pg> for SubscribedType { diff --git a/crates/utils/src/error.rs b/crates/utils/src/error.rs index b6a4fe4ec..7ac42f0bb 100644 --- a/crates/utils/src/error.rs +++ b/crates/utils/src/error.rs @@ -215,6 +215,7 @@ pub enum LemmyErrorType { InstanceBlockAlreadyExists, /// `jwt` cookie must be marked secure and httponly AuthCookieInsecure, + CommunityHasNoFollowers, UserBackupTooLarge, Unknown(String), } From 6cfbb8fc3b5f5b6cf92154517aa6184998906d3d Mon Sep 17 00:00:00 2001 From: Nutomic Date: Mon, 16 Oct 2023 18:36:53 +0200 Subject: [PATCH 09/24] Remove empty API responses (#3993) * Remove empty API responses * also remove change password response * fix invalidate * Run clippy. * Fixing api_test lints. --------- Co-authored-by: Dessalines Co-authored-by: Dessalines --- api_tests/package.json | 10 +- api_tests/src/comment.spec.ts | 1 - api_tests/src/community.spec.ts | 1 - api_tests/src/post.spec.ts | 12 +- api_tests/yarn.lock | 387 +++++++++++++----- .../local_user/change_password_after_reset.rs | 11 +- crates/api/src/local_user/reset_password.rs | 9 +- crates/api/src/local_user/verify_email.rs | 9 +- crates/api_common/src/person.rs | 18 - crates/api_crud/src/user/delete.rs | 9 +- 10 files changed, 305 insertions(+), 162 deletions(-) diff --git a/api_tests/package.json b/api_tests/package.json index 56624f9e2..84e5d3df5 100644 --- a/api_tests/package.json +++ b/api_tests/package.json @@ -13,11 +13,11 @@ }, "devDependencies": { "@types/jest": "^29.5.1", - "@types/node": "^20.1.2", - "@typescript-eslint/eslint-plugin": "^5.59.5", - "@typescript-eslint/parser": "^5.59.5", - "eslint": "^8.40.0", - "eslint-plugin-prettier": "^4.0.0", + "@types/node": "^20.8.6", + "@typescript-eslint/eslint-plugin": "^6.7.5", + "@typescript-eslint/parser": "^6.7.5", + "eslint": "^8.51.0", + "eslint-plugin-prettier": "^5.0.1", "jest": "^29.5.0", "lemmy-js-client": "0.19.0-rc.12", "prettier": "^3.0.0", diff --git a/api_tests/src/comment.spec.ts b/api_tests/src/comment.spec.ts index ed90621a7..6522a472e 100644 --- a/api_tests/src/comment.spec.ts +++ b/api_tests/src/comment.spec.ts @@ -32,7 +32,6 @@ import { getReplies, getUnreadCount, waitUntil, - delay, waitForPost, alphaUrl, followCommunity, diff --git a/api_tests/src/community.spec.ts b/api_tests/src/community.spec.ts index f4d06d588..22e602b19 100644 --- a/api_tests/src/community.spec.ts +++ b/api_tests/src/community.spec.ts @@ -26,7 +26,6 @@ import { blockInstance, waitUntil, delay, - waitForPost, alphaUrl, betaAllowedInstances, searchPostLocal, diff --git a/api_tests/src/post.spec.ts b/api_tests/src/post.spec.ts index 30a176fee..8c1f22226 100644 --- a/api_tests/src/post.spec.ts +++ b/api_tests/src/post.spec.ts @@ -39,8 +39,7 @@ import { loginUser, } from "./shared"; import { PostView } from "lemmy-js-client/dist/types/PostView"; -import { CreatePost } from "lemmy-js-client/dist/types/CreatePost"; -import { LemmyHttp, Login } from "lemmy-js-client"; +import { LemmyHttp } from "lemmy-js-client"; let betaCommunity: CommunityView | undefined; @@ -426,7 +425,7 @@ test("Enforce site ban for federated user", async () => { expect(alphaUserOnBeta1.person?.person.banned).toBe(true); // existing alpha post should be removed on beta - let searchBeta2 = await waitUntil( + await waitUntil( () => getPost(beta, searchBeta1.post.id), s => s.post_view.post.removed, ); @@ -441,13 +440,16 @@ test("Enforce site ban for federated user", async () => { expect(unBanAlpha.banned).toBe(false); // Login gets invalidated by ban, need to login again - let newAlphaUserJwt = await loginUser(alpha, alphaUserPerson?.name!); + if (!alphaUserPerson) { + throw "Missing alpha person"; + } + let newAlphaUserJwt = await loginUser(alpha, alphaUserPerson.name); alpha_user.setHeaders({ Authorization: "Bearer " + newAlphaUserJwt.jwt ?? "", }); // alpha makes new post in beta community, it federates let postRes2 = await createPost(alpha_user, betaCommunity!.community.id); - let searchBeta3 = await waitForPost(beta, postRes2.post_view.post); + await waitForPost(beta, postRes2.post_view.post); let alphaUserOnBeta2 = await resolvePerson(beta, alphaUserActorId!); expect(alphaUserOnBeta2.person?.person.banned).toBe(false); diff --git a/api_tests/yarn.lock b/api_tests/yarn.lock index 629f36216..56764e096 100644 --- a/api_tests/yarn.lock +++ b/api_tests/yarn.lock @@ -302,14 +302,14 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== -"@eslint-community/eslint-utils@^4.2.0": +"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": version "4.4.0" resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== dependencies: eslint-visitor-keys "^3.3.0" -"@eslint-community/regexpp@^4.4.0", "@eslint-community/regexpp@^4.6.1": +"@eslint-community/regexpp@^4.5.1", "@eslint-community/regexpp@^4.6.1": version "4.9.1" resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.9.1.tgz#449dfa81a57a1d755b09aa58d826c1262e4283b4" integrity sha512-Y27x+MBLjXa+0JWDhykM3+JE+il3kHKAEqabfEWq3SDhZjLYb6/BHL/JKFnH3fe207JaXkyDo685Oc2Glt6ifA== @@ -329,10 +329,10 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@eslint/js@8.50.0": - version "8.50.0" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.50.0.tgz#9e93b850f0f3fa35f5fa59adfd03adae8488e484" - integrity sha512-NCC3zz2+nvYd+Ckfh87rA47zfu2QsQpvc6k1yzTk+b9KzRj0wkGa8LSoGOXN6Zv4lRf/EIoZ80biDh9HOI+RNQ== +"@eslint/js@8.51.0": + version "8.51.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.51.0.tgz#6d419c240cfb2b66da37df230f7e7eef801c32fa" + integrity sha512-HxjQ8Qn+4SI3/AFv6sOrDB+g6PpUTDwSJiQqOrnneEk8L71161srI9gjzzZvYVbzHiVg/BvcH95+cK/zfIt4pg== "@humanwhocodes/config-array@^0.11.11": version "0.11.11" @@ -614,6 +614,18 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@pkgr/utils@^2.3.1": + version "2.4.2" + resolved "https://registry.yarnpkg.com/@pkgr/utils/-/utils-2.4.2.tgz#9e638bbe9a6a6f165580dc943f138fd3309a2cbc" + integrity sha512-POgTXhjrTfbTV63DiFXav4lBHiICLKKwDeaKn9Nphwj7WH6m0hMMCaJkMyRWjgtPFyRKRVoMXXjczsTQRDEhYw== + dependencies: + cross-spawn "^7.0.3" + fast-glob "^3.3.0" + is-glob "^4.0.3" + open "^9.1.0" + picocolors "^1.0.0" + tslib "^2.6.0" + "@sinclair/typebox@^0.27.8": version "0.27.8" resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" @@ -700,17 +712,24 @@ expect "^29.0.0" pretty-format "^29.0.0" -"@types/json-schema@^7.0.9": +"@types/json-schema@^7.0.12": version "7.0.13" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.13.tgz#02c24f4363176d2d18fc8b70b9f3c54aba178a85" integrity sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ== -"@types/node@*", "@types/node@^20.1.2": +"@types/node@*": version "20.8.0" resolved "https://registry.yarnpkg.com/@types/node/-/node-20.8.0.tgz#10ddf0119cf20028781c06d7115562934e53f745" integrity sha512-LzcWltT83s1bthcvjBmiBvGJiiUe84NWRHkw+ZV6Fr41z2FbIzvc815dk2nQ3RAKMuN2fkenM/z3Xv2QzEpYxQ== -"@types/semver@^7.3.12": +"@types/node@^20.8.6": + version "20.8.6" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.8.6.tgz#0dbd4ebcc82ad0128df05d0e6f57e05359ee47fa" + integrity sha512-eWO4K2Ji70QzKUqRy6oyJWUeB7+g2cRagT3T/nxYibYcT4y2BDL8lqolRXjTHmkZCdJfIPaY73KbJAZmcryxTQ== + dependencies: + undici-types "~5.25.1" + +"@types/semver@^7.5.0": version "7.5.3" resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.3.tgz#9a726e116beb26c24f1ccd6850201e1246122e04" integrity sha512-OxepLK9EuNEIPxWNME+C6WwbRAOOI2o2BaQEGzz5Lu2e4Z5eDnEo+/aVEDMIXywoJitJ7xWd641wrGLZdtwRyw== @@ -732,89 +751,90 @@ dependencies: "@types/yargs-parser" "*" -"@typescript-eslint/eslint-plugin@^5.59.5": - version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz#aeef0328d172b9e37d9bab6dbc13b87ed88977db" - integrity sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag== +"@typescript-eslint/eslint-plugin@^6.7.5": + version "6.7.5" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.7.5.tgz#f4024b9f63593d0c2b5bd6e4ca027e6f30934d4f" + integrity sha512-JhtAwTRhOUcP96D0Y6KYnwig/MRQbOoLGXTON2+LlyB/N35SP9j1boai2zzwXb7ypKELXMx3DVk9UTaEq1vHEw== dependencies: - "@eslint-community/regexpp" "^4.4.0" - "@typescript-eslint/scope-manager" "5.62.0" - "@typescript-eslint/type-utils" "5.62.0" - "@typescript-eslint/utils" "5.62.0" + "@eslint-community/regexpp" "^4.5.1" + "@typescript-eslint/scope-manager" "6.7.5" + "@typescript-eslint/type-utils" "6.7.5" + "@typescript-eslint/utils" "6.7.5" + "@typescript-eslint/visitor-keys" "6.7.5" debug "^4.3.4" graphemer "^1.4.0" - ignore "^5.2.0" - natural-compare-lite "^1.4.0" - semver "^7.3.7" - tsutils "^3.21.0" + ignore "^5.2.4" + natural-compare "^1.4.0" + semver "^7.5.4" + ts-api-utils "^1.0.1" -"@typescript-eslint/parser@^5.59.5": - version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.62.0.tgz#1b63d082d849a2fcae8a569248fbe2ee1b8a56c7" - integrity sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA== +"@typescript-eslint/parser@^6.7.5": + version "6.7.5" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.7.5.tgz#8d7ca3d1fbd9d5a58cc4d30b2aa797a760137886" + integrity sha512-bIZVSGx2UME/lmhLcjdVc7ePBwn7CLqKarUBL4me1C5feOd663liTGjMBGVcGr+BhnSLeP4SgwdvNnnkbIdkCw== dependencies: - "@typescript-eslint/scope-manager" "5.62.0" - "@typescript-eslint/types" "5.62.0" - "@typescript-eslint/typescript-estree" "5.62.0" + "@typescript-eslint/scope-manager" "6.7.5" + "@typescript-eslint/types" "6.7.5" + "@typescript-eslint/typescript-estree" "6.7.5" + "@typescript-eslint/visitor-keys" "6.7.5" debug "^4.3.4" -"@typescript-eslint/scope-manager@5.62.0": - version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz#d9457ccc6a0b8d6b37d0eb252a23022478c5460c" - integrity sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w== +"@typescript-eslint/scope-manager@6.7.5": + version "6.7.5" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.7.5.tgz#1cf33b991043886cd67f4f3600b8e122fc14e711" + integrity sha512-GAlk3eQIwWOJeb9F7MKQ6Jbah/vx1zETSDw8likab/eFcqkjSD7BI75SDAeC5N2L0MmConMoPvTsmkrg71+B1A== dependencies: - "@typescript-eslint/types" "5.62.0" - "@typescript-eslint/visitor-keys" "5.62.0" + "@typescript-eslint/types" "6.7.5" + "@typescript-eslint/visitor-keys" "6.7.5" -"@typescript-eslint/type-utils@5.62.0": - version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz#286f0389c41681376cdad96b309cedd17d70346a" - integrity sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew== +"@typescript-eslint/type-utils@6.7.5": + version "6.7.5" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-6.7.5.tgz#0a65949ec16588d8956f6d967f7d9c84ddb2d72a" + integrity sha512-Gs0qos5wqxnQrvpYv+pf3XfcRXW6jiAn9zE/K+DlmYf6FcpxeNYN0AIETaPR7rHO4K2UY+D0CIbDP9Ut0U4m1g== dependencies: - "@typescript-eslint/typescript-estree" "5.62.0" - "@typescript-eslint/utils" "5.62.0" + "@typescript-eslint/typescript-estree" "6.7.5" + "@typescript-eslint/utils" "6.7.5" debug "^4.3.4" - tsutils "^3.21.0" + ts-api-utils "^1.0.1" -"@typescript-eslint/types@5.62.0": - version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.62.0.tgz#258607e60effa309f067608931c3df6fed41fd2f" - integrity sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ== +"@typescript-eslint/types@6.7.5": + version "6.7.5" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.7.5.tgz#4571320fb9cf669de9a95d9849f922c3af809790" + integrity sha512-WboQBlOXtdj1tDFPyIthpKrUb+kZf2VroLZhxKa/VlwLlLyqv/PwUNgL30BlTVZV1Wu4Asu2mMYPqarSO4L5ZQ== -"@typescript-eslint/typescript-estree@5.62.0": - version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz#7d17794b77fabcac615d6a48fb143330d962eb9b" - integrity sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA== +"@typescript-eslint/typescript-estree@6.7.5": + version "6.7.5" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.7.5.tgz#4578de1a26e9f24950f029a4f00d1bfe41f15a39" + integrity sha512-NhJiJ4KdtwBIxrKl0BqG1Ur+uw7FiOnOThcYx9DpOGJ/Abc9z2xNzLeirCG02Ig3vkvrc2qFLmYSSsaITbKjlg== dependencies: - "@typescript-eslint/types" "5.62.0" - "@typescript-eslint/visitor-keys" "5.62.0" + "@typescript-eslint/types" "6.7.5" + "@typescript-eslint/visitor-keys" "6.7.5" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" - semver "^7.3.7" - tsutils "^3.21.0" + semver "^7.5.4" + ts-api-utils "^1.0.1" -"@typescript-eslint/utils@5.62.0": - version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.62.0.tgz#141e809c71636e4a75daa39faed2fb5f4b10df86" - integrity sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ== +"@typescript-eslint/utils@6.7.5": + version "6.7.5" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.7.5.tgz#ab847b53d6b65e029314b8247c2336843dba81ab" + integrity sha512-pfRRrH20thJbzPPlPc4j0UNGvH1PjPlhlCMq4Yx7EGjV7lvEeGX0U6MJYe8+SyFutWgSHsdbJ3BXzZccYggezA== dependencies: - "@eslint-community/eslint-utils" "^4.2.0" - "@types/json-schema" "^7.0.9" - "@types/semver" "^7.3.12" - "@typescript-eslint/scope-manager" "5.62.0" - "@typescript-eslint/types" "5.62.0" - "@typescript-eslint/typescript-estree" "5.62.0" - eslint-scope "^5.1.1" - semver "^7.3.7" + "@eslint-community/eslint-utils" "^4.4.0" + "@types/json-schema" "^7.0.12" + "@types/semver" "^7.5.0" + "@typescript-eslint/scope-manager" "6.7.5" + "@typescript-eslint/types" "6.7.5" + "@typescript-eslint/typescript-estree" "6.7.5" + semver "^7.5.4" -"@typescript-eslint/visitor-keys@5.62.0": - version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz#2174011917ce582875954ffe2f6912d5931e353e" - integrity sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw== +"@typescript-eslint/visitor-keys@6.7.5": + version "6.7.5" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.7.5.tgz#84c68d6ceb5b12d5246b918b84f2b79affd6c2f1" + integrity sha512-3MaWdDZtLlsexZzDSdQWsFQ9l9nL8B80Z4fImSpyllFC/KLqWQRdEcB+gGGO+N3Q2uL40EsG66wZLsohPxNXvg== dependencies: - "@typescript-eslint/types" "5.62.0" - eslint-visitor-keys "^3.3.0" + "@typescript-eslint/types" "6.7.5" + eslint-visitor-keys "^3.4.1" acorn-jsx@^5.3.2: version "5.3.2" @@ -962,6 +982,18 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== +big-integer@^1.6.44: + version "1.6.51" + resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.51.tgz#0df92a5d9880560d3ff2d5fd20245c889d130686" + integrity sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg== + +bplist-parser@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/bplist-parser/-/bplist-parser-0.2.0.tgz#43a9d183e5bf9d545200ceac3e712f79ebbe8d0e" + integrity sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw== + dependencies: + big-integer "^1.6.44" + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -1006,6 +1038,13 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== +bundle-name@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/bundle-name/-/bundle-name-3.0.0.tgz#ba59bcc9ac785fb67ccdbf104a2bf60c099f0e1a" + integrity sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw== + dependencies: + run-applescript "^5.0.0" + callsites@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" @@ -1174,6 +1213,29 @@ deepmerge@^4.2.2: resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== +default-browser-id@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/default-browser-id/-/default-browser-id-3.0.0.tgz#bee7bbbef1f4e75d31f98f4d3f1556a14cea790c" + integrity sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA== + dependencies: + bplist-parser "^0.2.0" + untildify "^4.0.0" + +default-browser@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/default-browser/-/default-browser-4.0.0.tgz#53c9894f8810bf86696de117a6ce9085a3cbc7da" + integrity sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA== + dependencies: + bundle-name "^3.0.0" + default-browser-id "^3.0.0" + execa "^7.1.1" + titleize "^3.0.0" + +define-lazy-prop@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz#dbb19adfb746d7fc6d734a06b72f4a00d021255f" + integrity sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg== + delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" @@ -1245,20 +1307,13 @@ escape-string-regexp@^4.0.0: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== -eslint-plugin-prettier@^4.0.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz#651cbb88b1dab98bfd42f017a12fa6b2d993f94b" - integrity sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ== +eslint-plugin-prettier@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.0.1.tgz#a3b399f04378f79f066379f544e42d6b73f11515" + integrity sha512-m3u5RnR56asrwV/lDC4GHorlW75DsFfmUcjfCYylTUs85dBRnB7VM6xG8eCMJdeDRnppzmxZVf1GEPJvl1JmNg== dependencies: prettier-linter-helpers "^1.0.0" - -eslint-scope@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" - integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== - dependencies: - esrecurse "^4.3.0" - estraverse "^4.1.1" + synckit "^0.8.5" eslint-scope@^7.2.2: version "7.2.2" @@ -1273,15 +1328,15 @@ eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4 resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== -eslint@^8.40.0: - version "8.50.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.50.0.tgz#2ae6015fee0240fcd3f83e1e25df0287f487d6b2" - integrity sha512-FOnOGSuFuFLv/Sa+FDVRZl4GGVAAFFi8LecRsI5a1tMO5HIE8nCm4ivAlzt4dT3ol/PaaGC0rJEEXQmHJBGoOg== +eslint@^8.51.0: + version "8.51.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.51.0.tgz#4a82dae60d209ac89a5cff1604fea978ba4950f3" + integrity sha512-2WuxRZBrlwnXi+/vFSJyjMqrNjtJqiasMzehF0shoLaW7DzS3/9Yvrmq5JiT66+pNjiX4UBnLDiKHcWAr/OInA== dependencies: "@eslint-community/eslint-utils" "^4.2.0" "@eslint-community/regexpp" "^4.6.1" "@eslint/eslintrc" "^2.1.2" - "@eslint/js" "8.50.0" + "@eslint/js" "8.51.0" "@humanwhocodes/config-array" "^0.11.11" "@humanwhocodes/module-importer" "^1.0.1" "@nodelib/fs.walk" "^1.2.8" @@ -1344,11 +1399,6 @@ esrecurse@^4.3.0: dependencies: estraverse "^5.2.0" -estraverse@^4.1.1: - version "4.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" - integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== - estraverse@^5.1.0, estraverse@^5.2.0: version "5.3.0" resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" @@ -1374,6 +1424,21 @@ execa@^5.0.0: signal-exit "^3.0.3" strip-final-newline "^2.0.0" +execa@^7.1.1: + version "7.2.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-7.2.0.tgz#657e75ba984f42a70f38928cedc87d6f2d4fe4e9" + integrity sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.1" + human-signals "^4.3.0" + is-stream "^3.0.0" + merge-stream "^2.0.0" + npm-run-path "^5.1.0" + onetime "^6.0.0" + signal-exit "^3.0.7" + strip-final-newline "^3.0.0" + exit@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" @@ -1400,7 +1465,7 @@ fast-diff@^1.1.2: resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0" integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw== -fast-glob@^3.2.9: +fast-glob@^3.2.9, fast-glob@^3.3.0: version "3.3.1" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.1.tgz#784b4e897340f3dbbef17413b3f11acf03c874c4" integrity sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg== @@ -1518,7 +1583,7 @@ get-package-type@^0.1.0: resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== -get-stream@^6.0.0: +get-stream@^6.0.0, get-stream@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== @@ -1610,7 +1675,12 @@ human-signals@^2.1.0: resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== -ignore@^5.2.0: +human-signals@^4.3.0: + version "4.3.1" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-4.3.1.tgz#ab7f811e851fca97ffbd2c1fe9a958964de321b2" + integrity sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ== + +ignore@^5.2.0, ignore@^5.2.4: version "5.2.4" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324" integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ== @@ -1661,6 +1731,16 @@ is-core-module@^2.13.0: dependencies: has "^1.0.3" +is-docker@^2.0.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" + integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== + +is-docker@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-3.0.0.tgz#90093aa3106277d8a77a5910dbae71747e15a200" + integrity sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ== + is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" @@ -1683,6 +1763,13 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: dependencies: is-extglob "^2.1.1" +is-inside-container@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-inside-container/-/is-inside-container-1.0.0.tgz#e81fba699662eb31dbdaf26766a61d4814717ea4" + integrity sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA== + dependencies: + is-docker "^3.0.0" + is-number@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" @@ -1698,6 +1785,18 @@ is-stream@^2.0.0: resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== +is-stream@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-3.0.0.tgz#e6bfd7aa6bef69f4f472ce9bb681e3e57b4319ac" + integrity sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA== + +is-wsl@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" + integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== + dependencies: + is-docker "^2.0.0" + isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" @@ -2294,6 +2393,11 @@ mimic-fn@^2.1.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== +mimic-fn@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-4.0.0.tgz#60a90550d5cb0b239cca65d893b1a53b29871ecc" + integrity sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw== + minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" @@ -2306,11 +2410,6 @@ ms@2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -natural-compare-lite@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz#17b09581988979fddafe0201e931ba933c96cbb4" - integrity sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g== - natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" @@ -2345,6 +2444,13 @@ npm-run-path@^4.0.1: dependencies: path-key "^3.0.0" +npm-run-path@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-5.1.0.tgz#bc62f7f3f6952d9894bd08944ba011a6ee7b7e00" + integrity sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q== + dependencies: + path-key "^4.0.0" + once@^1.3.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" @@ -2359,6 +2465,23 @@ onetime@^5.1.2: dependencies: mimic-fn "^2.1.0" +onetime@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-6.0.0.tgz#7c24c18ed1fd2e9bca4bd26806a33613c77d34b4" + integrity sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ== + dependencies: + mimic-fn "^4.0.0" + +open@^9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/open/-/open-9.1.0.tgz#684934359c90ad25742f5a26151970ff8c6c80b6" + integrity sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg== + dependencies: + default-browser "^4.0.0" + define-lazy-prop "^3.0.0" + is-inside-container "^1.0.0" + is-wsl "^2.2.0" + optionator@^0.9.3: version "0.9.3" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.3.tgz#007397d44ed1872fdc6ed31360190f81814e2c64" @@ -2436,6 +2559,11 @@ path-key@^3.0.0, path-key@^3.1.0: resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== +path-key@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-4.0.0.tgz#295588dc3aee64154f877adb9d780b81c554bf18" + integrity sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ== + path-parse@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" @@ -2570,6 +2698,13 @@ rimraf@^3.0.2: dependencies: glob "^7.1.3" +run-applescript@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/run-applescript/-/run-applescript-5.0.0.tgz#e11e1c932e055d5c6b40d98374e0268d9b11899c" + integrity sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg== + dependencies: + execa "^5.0.0" + run-parallel@^1.1.9: version "1.2.0" resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" @@ -2582,7 +2717,7 @@ semver@^6.3.0, semver@^6.3.1: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.3.7, semver@^7.5.3, semver@^7.5.4: +semver@^7.5.3, semver@^7.5.4: version "7.5.4" resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== @@ -2675,6 +2810,11 @@ strip-final-newline@^2.0.0: resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== +strip-final-newline@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-3.0.0.tgz#52894c313fbff318835280aed60ff71ebf12b8fd" + integrity sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw== + strip-json-comments@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" @@ -2706,6 +2846,14 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== +synckit@^0.8.5: + version "0.8.5" + resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.8.5.tgz#b7f4358f9bb559437f9f167eb6bc46b3c9818fa3" + integrity sha512-L1dapNV6vu2s/4Sputv8xGsCdAVlb5nRDMFU/E27D44l5U6cw1g0dGd45uLc+OXjNMmF4ntiMdCimzcjFKQI8Q== + dependencies: + "@pkgr/utils" "^2.3.1" + tslib "^2.5.0" + test-exclude@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" @@ -2720,6 +2868,11 @@ text-table@^0.2.0: resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== +titleize@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/titleize/-/titleize-3.0.0.tgz#71c12eb7fdd2558aa8a44b0be83b8a76694acd53" + integrity sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ== + tmpl@1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" @@ -2742,6 +2895,11 @@ tr46@~0.0.3: resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== +ts-api-utils@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.0.3.tgz#f12c1c781d04427313dbac808f453f050e54a331" + integrity sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg== + ts-jest@^29.1.0: version "29.1.1" resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.1.1.tgz#f58fe62c63caf7bfcc5cc6472082f79180f0815b" @@ -2756,17 +2914,10 @@ ts-jest@^29.1.0: semver "^7.5.3" yargs-parser "^21.0.1" -tslib@^1.8.1: - version "1.14.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" - integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== - -tsutils@^3.21.0: - version "3.21.0" - resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" - integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== - dependencies: - tslib "^1.8.1" +tslib@^2.5.0, tslib@^2.6.0: + version "2.6.2" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" + integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" @@ -2795,6 +2946,16 @@ typescript@^5.0.4: resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.2.2.tgz#5ebb5e5a5b75f085f22bc3f8460fba308310fa78" integrity sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w== +undici-types@~5.25.1: + version "5.25.3" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.25.3.tgz#e044115914c85f0bcbb229f346ab739f064998c3" + integrity sha512-Ga1jfYwRn7+cP9v8auvEXN1rX3sWqlayd4HP7OKk4mZWylEmu3KzXDUGrQUN6Ol7qo1gPvB2e5gX6udnyEPgdA== + +untildify@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b" + integrity sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw== + update-browserslist-db@^1.0.13: version "1.0.13" resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz#3c5e4f5c083661bd38ef64b6328c26ed6c8248c4" diff --git a/crates/api/src/local_user/change_password_after_reset.rs b/crates/api/src/local_user/change_password_after_reset.rs index b3e193dc7..50a267d6a 100644 --- a/crates/api/src/local_user/change_password_after_reset.rs +++ b/crates/api/src/local_user/change_password_after_reset.rs @@ -1,8 +1,9 @@ use actix_web::web::{Data, Json}; use lemmy_api_common::{ context::LemmyContext, - person::{LoginResponse, PasswordChangeAfterReset}, + person::PasswordChangeAfterReset, utils::password_length_check, + SuccessResponse, }; use lemmy_db_schema::source::{ local_user::LocalUser, @@ -15,7 +16,7 @@ use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; pub async fn change_password_after_reset( data: Json, context: Data, -) -> Result, LemmyError> { +) -> Result, LemmyError> { // Fetch the user_id from the token let token = data.token.clone(); let local_user_id = PasswordResetRequest::read_from_token(&mut context.pool(), &token) @@ -37,9 +38,5 @@ pub async fn change_password_after_reset( LoginToken::invalidate_all(&mut context.pool(), local_user_id).await?; - Ok(Json(LoginResponse { - jwt: None, - verify_email_sent: false, - registration_created: false, - })) + Ok(Json(SuccessResponse::default())) } diff --git a/crates/api/src/local_user/reset_password.rs b/crates/api/src/local_user/reset_password.rs index 6bdb6f8ef..90aa910e0 100644 --- a/crates/api/src/local_user/reset_password.rs +++ b/crates/api/src/local_user/reset_password.rs @@ -1,18 +1,19 @@ use actix_web::web::{Data, Json}; use lemmy_api_common::{ context::LemmyContext, - person::{PasswordReset, PasswordResetResponse}, + person::PasswordReset, utils::send_password_reset_email, + SuccessResponse, }; use lemmy_db_schema::source::password_reset_request::PasswordResetRequest; use lemmy_db_views::structs::LocalUserView; -use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; +use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult}; #[tracing::instrument(skip(context))] pub async fn reset_password( data: Json, context: Data, -) -> Result, LemmyError> { +) -> LemmyResult> { // Fetch that email let email = data.email.to_lowercase(); let local_user_view = LocalUserView::find_by_email(&mut context.pool(), &email) @@ -31,5 +32,5 @@ pub async fn reset_password( // Email the pure token to the user. send_password_reset_email(&local_user_view, &mut context.pool(), context.settings()).await?; - Ok(Json(PasswordResetResponse {})) + Ok(Json(SuccessResponse::default())) } diff --git a/crates/api/src/local_user/verify_email.rs b/crates/api/src/local_user/verify_email.rs index d3b549ccb..94ddb373a 100644 --- a/crates/api/src/local_user/verify_email.rs +++ b/crates/api/src/local_user/verify_email.rs @@ -1,8 +1,9 @@ use actix_web::web::{Data, Json}; use lemmy_api_common::{ context::LemmyContext, - person::{VerifyEmail, VerifyEmailResponse}, + person::VerifyEmail, utils::send_new_applicant_email_to_admins, + SuccessResponse, }; use lemmy_db_schema::{ source::{ @@ -14,12 +15,12 @@ use lemmy_db_schema::{ RegistrationMode, }; use lemmy_db_views::structs::SiteView; -use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; +use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult}; pub async fn verify_email( data: Json, context: Data, -) -> Result, LemmyError> { +) -> LemmyResult> { let site_view = SiteView::read_local(&mut context.pool()).await?; let token = data.token.clone(); let verification = EmailVerification::read_for_token(&mut context.pool(), &token) @@ -48,5 +49,5 @@ pub async fn verify_email( .await?; } - Ok(Json(VerifyEmailResponse {})) + Ok(Json(SuccessResponse::default())) } diff --git a/crates/api_common/src/person.rs b/crates/api_common/src/person.rs index 07ef91151..c410cbbe8 100644 --- a/crates/api_common/src/person.rs +++ b/crates/api_common/src/person.rs @@ -331,12 +331,6 @@ pub struct DeleteAccount { pub delete_content: bool, } -#[derive(Debug, Serialize, Deserialize, Clone)] -#[cfg_attr(feature = "full", derive(TS))] -#[cfg_attr(feature = "full", ts(export))] -/// The response of deleting your account. -pub struct DeleteAccountResponse {} - #[derive(Debug, Serialize, Deserialize, Clone, Default)] #[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", ts(export))] @@ -345,12 +339,6 @@ pub struct PasswordReset { pub email: Sensitive, } -#[derive(Debug, Serialize, Deserialize, Clone)] -#[cfg_attr(feature = "full", derive(TS))] -#[cfg_attr(feature = "full", ts(export))] -/// The response of a password reset. -pub struct PasswordResetResponse {} - #[derive(Debug, Serialize, Deserialize, Clone, Default)] #[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", ts(export))] @@ -400,12 +388,6 @@ pub struct VerifyEmail { pub token: String, } -#[derive(Debug, Serialize, Deserialize, Clone)] -#[cfg_attr(feature = "full", derive(TS))] -#[cfg_attr(feature = "full", ts(export))] -/// A response to verifying your email. -pub struct VerifyEmailResponse {} - #[derive(Debug, Serialize, Deserialize, Clone)] #[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", ts(export))] diff --git a/crates/api_crud/src/user/delete.rs b/crates/api_crud/src/user/delete.rs index 8df9c5204..363230d83 100644 --- a/crates/api_crud/src/user/delete.rs +++ b/crates/api_crud/src/user/delete.rs @@ -3,20 +3,21 @@ use actix_web::web::Json; use bcrypt::verify; use lemmy_api_common::{ context::LemmyContext, - person::{DeleteAccount, DeleteAccountResponse}, + person::DeleteAccount, send_activity::{ActivityChannel, SendActivityData}, utils::purge_user_account, + SuccessResponse, }; use lemmy_db_schema::source::{login_token::LoginToken, person::Person}; use lemmy_db_views::structs::LocalUserView; -use lemmy_utils::error::{LemmyError, LemmyErrorType}; +use lemmy_utils::error::{LemmyErrorType, LemmyResult}; #[tracing::instrument(skip(context))] pub async fn delete_account( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> LemmyResult> { // Verify the password let valid: bool = verify( &data.password, @@ -41,5 +42,5 @@ pub async fn delete_account( ) .await?; - Ok(Json(DeleteAccountResponse {})) + Ok(Json(SuccessResponse::default())) } From 332e6983366fbb2cda2c8e9ecb88a0bd37b48ec4 Mon Sep 17 00:00:00 2001 From: Nutomic Date: Tue, 17 Oct 2023 01:37:28 +0200 Subject: [PATCH 10/24] Cleanup public api (#4047) * Convert PersonSortType to purely internal * Remove hot rank and other db optimizations from public api --- Cargo.lock | 2 ++ crates/apub/src/api/search.rs | 6 ++--- crates/db_schema/src/aggregates/structs.rs | 16 +++++++++++- crates/db_schema/src/lib.rs | 13 ---------- crates/db_schema/src/utils.rs | 11 -------- crates/db_views_actor/Cargo.toml | 2 ++ crates/db_views_actor/src/person_view.rs | 30 +++++++++++++++++++--- 7 files changed, 49 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 36bbfce8e..5cf23c6a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2808,6 +2808,8 @@ dependencies = [ "lemmy_db_schema", "serde", "serde_with", + "strum", + "strum_macros", "ts-rs", ] diff --git a/crates/apub/src/api/search.rs b/crates/apub/src/api/search.rs index 0c7231e8f..b854a91d1 100644 --- a/crates/apub/src/api/search.rs +++ b/crates/apub/src/api/search.rs @@ -8,7 +8,7 @@ use lemmy_api_common::{ }; use lemmy_db_schema::{ source::{community::Community, local_site::LocalSite}, - utils::{post_to_comment_sort_type, post_to_person_sort_type}, + utils::post_to_comment_sort_type, SearchType, }; use lemmy_db_views::{comment_view::CommentQuery, post_view::PostQuery, structs::LocalUserView}; @@ -101,7 +101,7 @@ pub async fn search( } SearchType::Users => { users = PersonQuery { - sort: (sort.map(post_to_person_sort_type)), + sort, search_term: (Some(q)), page: (page), limit: (limit), @@ -171,7 +171,7 @@ pub async fn search( vec![] } else { PersonQuery { - sort: (sort.map(post_to_person_sort_type)), + sort, search_term: (Some(q)), page: (page), limit: (limit), diff --git a/crates/db_schema/src/aggregates/structs.rs b/crates/db_schema/src/aggregates/structs.rs index 03ff9a640..641ca3b3d 100644 --- a/crates/db_schema/src/aggregates/structs.rs +++ b/crates/db_schema/src/aggregates/structs.rs @@ -27,7 +27,9 @@ pub struct CommentAggregates { pub published: DateTime, /// The total number of children in this comment branch. pub child_count: i32, + #[serde(skip)] pub hot_rank: f64, + #[serde(skip)] pub controversy_rank: f64, } @@ -55,6 +57,7 @@ pub struct CommunityAggregates { pub users_active_month: i64, /// The number of users with any activity in the last year. pub users_active_half_year: i64, + #[serde(skip)] pub hot_rank: f64, } @@ -87,21 +90,32 @@ pub struct PostAggregates { pub upvotes: i64, pub downvotes: i64, pub published: DateTime, - /// A newest comment time, limited to 2 days, to prevent necrobumping + #[serde(skip)] + /// A newest comment time, limited to 2 days, to prevent necrobumping pub newest_comment_time_necro: DateTime, /// The time of the newest comment in the post. + #[serde(skip)] pub newest_comment_time: DateTime, /// If the post is featured on the community. + #[serde(skip)] pub featured_community: bool, /// If the post is featured on the site / to local. + #[serde(skip)] pub featured_local: bool, + #[serde(skip)] pub hot_rank: f64, + #[serde(skip)] pub hot_rank_active: f64, + #[serde(skip)] pub community_id: CommunityId, + #[serde(skip)] pub creator_id: PersonId, + #[serde(skip)] pub controversy_rank: f64, + #[serde(skip)] pub instance_id: InstanceId, /// A rank that amplifies smaller communities + #[serde(skip)] pub scaled_rank: f64, } diff --git a/crates/db_schema/src/lib.rs b/crates/db_schema/src/lib.rs index 80fc9ffb7..5b908f356 100644 --- a/crates/db_schema/src/lib.rs +++ b/crates/db_schema/src/lib.rs @@ -91,19 +91,6 @@ pub enum CommentSortType { Controversial, } -#[derive(EnumString, Display, Debug, Serialize, Deserialize, Clone, Copy)] -#[cfg_attr(feature = "full", derive(TS))] -#[cfg_attr(feature = "full", ts(export))] -/// The person sort types. See here for descriptions: https://join-lemmy.org/docs/en/users/03-votes-and-ranking.html -pub enum PersonSortType { - New, - Old, - MostComments, - CommentScore, - PostScore, - PostCount, -} - #[derive( EnumString, Display, Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Default, )] diff --git a/crates/db_schema/src/utils.rs b/crates/db_schema/src/utils.rs index 7593cfd41..7e83569a7 100644 --- a/crates/db_schema/src/utils.rs +++ b/crates/db_schema/src/utils.rs @@ -3,7 +3,6 @@ use crate::{ diesel_migrations::MigrationHarness, newtypes::DbUrl, CommentSortType, - PersonSortType, SortType, }; use activitypub_federation::{fetch::object_id::ObjectId, traits::Object}; @@ -365,16 +364,6 @@ pub fn post_to_comment_sort_type(sort: SortType) -> CommentSortType { } } -pub fn post_to_person_sort_type(sort: SortType) -> PersonSortType { - match sort { - SortType::Active | SortType::Hot | SortType::Controversial => PersonSortType::CommentScore, - SortType::New | SortType::NewComments => PersonSortType::New, - SortType::MostComments => PersonSortType::MostComments, - SortType::Old => PersonSortType::Old, - _ => PersonSortType::CommentScore, - } -} - static EMAIL_REGEX: Lazy = Lazy::new(|| { Regex::new(r"^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$") .expect("compile email regex") diff --git a/crates/db_views_actor/Cargo.toml b/crates/db_views_actor/Cargo.toml index 20b69e56c..358bf0cab 100644 --- a/crates/db_views_actor/Cargo.toml +++ b/crates/db_views_actor/Cargo.toml @@ -29,3 +29,5 @@ serde = { workspace = true } serde_with = { workspace = true } ts-rs = { workspace = true, optional = true } chrono.workspace = true +strum = { workspace = true } +strum_macros = { workspace = true } diff --git a/crates/db_views_actor/src/person_view.rs b/crates/db_views_actor/src/person_view.rs index 042b04767..d06654f98 100644 --- a/crates/db_views_actor/src/person_view.rs +++ b/crates/db_views_actor/src/person_view.rs @@ -14,8 +14,10 @@ use lemmy_db_schema::{ schema, schema::{local_user, person, person_aggregates}, utils::{fuzzy_search, get_conn, limit_and_offset, now, DbConn, DbPool, ListFn, Queries, ReadFn}, - PersonSortType, + SortType, }; +use serde::{Deserialize, Serialize}; +use strum_macros::{Display, EnumString}; enum ListMode { Admins, @@ -23,6 +25,27 @@ enum ListMode { Query(PersonQuery), } +#[derive(EnumString, Display, Debug, Serialize, Deserialize, Clone, Copy)] +/// The person sort types. Converted automatically from `SortType` +enum PersonSortType { + New, + Old, + MostComments, + CommentScore, + PostScore, + PostCount, +} + +fn post_to_person_sort_type(sort: SortType) -> PersonSortType { + match sort { + SortType::Active | SortType::Hot | SortType::Controversial => PersonSortType::CommentScore, + SortType::New | SortType::NewComments => PersonSortType::New, + SortType::MostComments => PersonSortType::MostComments, + SortType::Old => PersonSortType::Old, + _ => PersonSortType::CommentScore, + } +} + fn queries<'a>( ) -> Queries, impl ListFn<'a, PersonView, ListMode>> { let all_joins = |query: person::BoxedQuery<'a, Pg>| { @@ -66,7 +89,8 @@ fn queries<'a>( .or_filter(person::display_name.ilike(searcher)); } - query = match options.sort.unwrap_or(PersonSortType::CommentScore) { + let sort = options.sort.map(post_to_person_sort_type); + query = match sort.unwrap_or(PersonSortType::CommentScore) { PersonSortType::New => query.order_by(person::published.desc()), PersonSortType::Old => query.order_by(person::published.asc()), PersonSortType::MostComments => query.order_by(person_aggregates::comment_count.desc()), @@ -116,7 +140,7 @@ impl PersonView { #[derive(Default)] pub struct PersonQuery { - pub sort: Option, + pub sort: Option, pub search_term: Option, pub page: Option, pub limit: Option, From 56322c75f04b0b7af83e7100602e162e49793d2d Mon Sep 17 00:00:00 2001 From: Nutomic Date: Tue, 17 Oct 2023 16:34:38 +0200 Subject: [PATCH 11/24] Read community follower count from home instance (fixes #1440) (#4013) * Read community follower count from home instance (fixes #1440) * fmt * prettier * fix tests * fmt * rename fn * fmt * Run prettier * increase timeout * ci --------- Co-authored-by: Dessalines Co-authored-by: Dessalines --- api_tests/src/community.spec.ts | 62 ++++++++++++++++- crates/apub/src/api/user_settings_backup.rs | 2 +- .../src/collections/community_follower.rs | 66 +++++++++++++++++++ crates/apub/src/collections/mod.rs | 1 + crates/apub/src/http/community.rs | 4 +- crates/apub/src/objects/community.rs | 16 +++-- .../protocol/collections/group_followers.rs | 30 ++------- crates/apub/src/protocol/objects/group.rs | 3 +- .../src/aggregates/community_aggregates.rs | 22 ++++++- .../down.sql | 24 +++++++ .../up.sql | 34 ++++++++++ 11 files changed, 223 insertions(+), 41 deletions(-) create mode 100644 crates/apub/src/collections/community_follower.rs create mode 100644 migrations/2023-10-02-145002_community_followers_count_federated/down.sql create mode 100644 migrations/2023-10-02-145002_community_followers_count_federated/up.sql diff --git a/api_tests/src/community.spec.ts b/api_tests/src/community.spec.ts index 22e602b19..2c97d629f 100644 --- a/api_tests/src/community.spec.ts +++ b/api_tests/src/community.spec.ts @@ -27,8 +27,11 @@ import { waitUntil, delay, alphaUrl, + delta, betaAllowedInstances, searchPostLocal, + resolveBetaCommunity, + longDelay, } from "./shared"; import { EditSite, LemmyHttp } from "lemmy-js-client"; @@ -378,6 +381,59 @@ test("User blocks instance, communities are hidden", async () => { expect(listing_ids3).toContain(postRes.post_view.post.ap_id); }); +test("Community follower count is federated", async () => { + // Follow the beta community from alpha + let resolved = await resolveBetaCommunity(alpha); + if (!resolved.community) { + throw "Missing beta community"; + } + + await followCommunity(alpha, true, resolved.community.community.id); + let followed = ( + await waitUntil( + () => resolveBetaCommunity(alpha), + c => c.community?.subscribed === "Subscribed", + ) + ).community; + + // Make sure there is 1 subscriber + expect(followed?.counts.subscribers).toBe(1); + + // Follow the community from gamma + resolved = await resolveBetaCommunity(gamma); + if (!resolved.community) { + throw "Missing beta community"; + } + + await followCommunity(gamma, true, resolved.community.community.id); + followed = ( + await waitUntil( + () => resolveBetaCommunity(gamma), + c => c.community?.subscribed === "Subscribed", + ) + ).community; + + // Make sure there are 2 subscribers + expect(followed?.counts?.subscribers).toBe(2); + + // Follow the community from delta + resolved = await resolveBetaCommunity(delta); + if (!resolved.community) { + throw "Missing beta community"; + } + + await followCommunity(delta, true, resolved.community.community.id); + followed = ( + await waitUntil( + () => resolveBetaCommunity(delta), + c => c.community?.subscribed === "Subscribed", + ) + ).community; + + // Make sure there are 3 subscribers + expect(followed?.counts?.subscribers).toBe(3); +}); + test("Dont receive community activities after unsubscribe", async () => { let communityRes = await createCommunity(alpha); expect(communityRes.community_view.community.name).toBeDefined(); @@ -402,7 +458,7 @@ test("Dont receive community activities after unsubscribe", async () => { let editSiteForm: EditSite = {}; editSiteForm.allowed_instances = ["lemmy-epsilon"]; await beta.editSite(editSiteForm); - await delay(2000); + await longDelay(); // unfollow await followCommunity(beta, false, betaCommunity!.community.id); @@ -417,7 +473,7 @@ test("Dont receive community activities after unsubscribe", async () => { // unblock alpha editSiteForm.allowed_instances = betaAllowedInstances; await beta.editSite(editSiteForm); - await delay(2000); + await longDelay(); // create a post, it shouldnt reach beta let postRes = await createPost( @@ -425,7 +481,7 @@ test("Dont receive community activities after unsubscribe", async () => { communityRes.community_view.community.id, ); expect(postRes.post_view.post.id).toBeDefined(); - await delay(2000); + // await longDelay(); let postResBeta = searchPostLocal(beta, postRes.post_view.post); expect((await postResBeta).posts.length).toBe(0); diff --git a/crates/apub/src/api/user_settings_backup.rs b/crates/apub/src/api/user_settings_backup.rs index d48260515..3a9b05847 100644 --- a/crates/apub/src/api/user_settings_backup.rs +++ b/crates/apub/src/api/user_settings_backup.rs @@ -375,7 +375,7 @@ mod tests { .unwrap(); // wait for background task to finish - sleep(Duration::from_millis(100)).await; + sleep(Duration::from_millis(1000)).await; let import_user_updated = LocalUserView::read(&mut context.pool(), import_user.local_user.id) .await diff --git a/crates/apub/src/collections/community_follower.rs b/crates/apub/src/collections/community_follower.rs new file mode 100644 index 000000000..da0e52069 --- /dev/null +++ b/crates/apub/src/collections/community_follower.rs @@ -0,0 +1,66 @@ +use crate::{ + objects::community::ApubCommunity, + protocol::collections::group_followers::GroupFollowers, +}; +use activitypub_federation::{ + config::Data, + kinds::collection::CollectionType, + protocol::verification::verify_domains_match, + traits::Collection, +}; +use lemmy_api_common::{context::LemmyContext, utils::generate_followers_url}; +use lemmy_db_schema::aggregates::structs::CommunityAggregates; +use lemmy_db_views_actor::structs::CommunityFollowerView; +use lemmy_utils::error::LemmyError; +use url::Url; + +#[derive(Clone, Debug)] +pub(crate) struct ApubCommunityFollower(Vec<()>); + +#[async_trait::async_trait] +impl Collection for ApubCommunityFollower { + type Owner = ApubCommunity; + type DataType = LemmyContext; + type Kind = GroupFollowers; + type Error = LemmyError; + + async fn read_local( + community: &Self::Owner, + context: &Data, + ) -> Result { + let community_id = community.id; + let community_followers = + CommunityFollowerView::count_community_followers(&mut context.pool(), community_id).await?; + + Ok(GroupFollowers { + id: generate_followers_url(&community.actor_id)?.into(), + r#type: CollectionType::Collection, + total_items: community_followers as i32, + items: vec![], + }) + } + + async fn verify( + json: &Self::Kind, + expected_domain: &Url, + _data: &Data, + ) -> Result<(), Self::Error> { + verify_domains_match(expected_domain, &json.id)?; + Ok(()) + } + + async fn from_json( + json: Self::Kind, + community: &Self::Owner, + context: &Data, + ) -> Result { + CommunityAggregates::update_federated_followers( + &mut context.pool(), + community.id, + json.total_items, + ) + .await?; + + Ok(ApubCommunityFollower(Vec::new())) + } +} diff --git a/crates/apub/src/collections/mod.rs b/crates/apub/src/collections/mod.rs index 32922bef8..32b48ea9a 100644 --- a/crates/apub/src/collections/mod.rs +++ b/crates/apub/src/collections/mod.rs @@ -1,3 +1,4 @@ pub(crate) mod community_featured; +pub(crate) mod community_follower; pub(crate) mod community_moderators; pub(crate) mod community_outbox; diff --git a/crates/apub/src/http/community.rs b/crates/apub/src/http/community.rs index d004bac56..3c964fe0a 100644 --- a/crates/apub/src/http/community.rs +++ b/crates/apub/src/http/community.rs @@ -2,12 +2,12 @@ use crate::{ activity_lists::GroupInboxActivities, collections::{ community_featured::ApubCommunityFeatured, + community_follower::ApubCommunityFollower, community_moderators::ApubCommunityModerators, community_outbox::ApubCommunityOutbox, }, http::{create_apub_response, create_apub_tombstone_response}, objects::{community::ApubCommunity, person::ApubPerson}, - protocol::collections::group_followers::GroupFollowers, }; use activitypub_federation::{ actix_web::inbox::receive_activity, @@ -66,7 +66,7 @@ pub(crate) async fn get_apub_community_followers( ) -> Result { let community = Community::read_from_name(&mut context.pool(), &info.community_name, false).await?; - let followers = GroupFollowers::new(community, &context).await?; + let followers = ApubCommunityFollower::read_local(&community.into(), &context).await?; create_apub_response(&followers) } diff --git a/crates/apub/src/objects/community.rs b/crates/apub/src/objects/community.rs index 3e8c62746..d88f457bd 100644 --- a/crates/apub/src/objects/community.rs +++ b/crates/apub/src/objects/community.rs @@ -146,15 +146,19 @@ impl Object for ApubCommunity { // Fetching mods and outbox is not necessary for Lemmy to work, so ignore errors. Besides, // we need to ignore these errors so that tests can work entirely offline. let fetch_outbox = group.outbox.dereference(&community, context); + let fetch_followers = group.followers.dereference(&community, context); if let Some(moderators) = group.attributed_to { let fetch_moderators = moderators.dereference(&community, context); - // Fetch mods and outbox in parallel - let res = tokio::join!(fetch_outbox, fetch_moderators); + // Fetch mods, outbox and followers in parallel + let res = tokio::join!(fetch_outbox, fetch_moderators, fetch_followers); res.0.map_err(|e| debug!("{}", e)).ok(); res.1.map_err(|e| debug!("{}", e)).ok(); + res.2.map_err(|e| debug!("{}", e)).ok(); } else { - fetch_outbox.await.map_err(|e| debug!("{}", e)).ok(); + let res = tokio::join!(fetch_outbox, fetch_followers); + res.0.map_err(|e| debug!("{}", e)).ok(); + res.1.map_err(|e| debug!("{}", e)).ok(); } Ok(community) @@ -235,12 +239,14 @@ pub(crate) mod tests { json.attributed_to = None; json.outbox = CollectionId::parse("https://enterprise.lemmy.ml/c/tenforward/not_outbox").unwrap(); + json.followers = + CollectionId::parse("https://enterprise.lemmy.ml/c/tenforward/not_followers").unwrap(); let url = Url::parse("https://enterprise.lemmy.ml/c/tenforward").unwrap(); ApubCommunity::verify(&json, &url, &context2).await.unwrap(); let community = ApubCommunity::from_json(json, &context2).await.unwrap(); - // this makes one requests to the (intentionally broken) outbox collection - assert_eq!(context2.request_count(), 1); + // this makes requests to the (intentionally broken) outbox and followers collections + assert_eq!(context2.request_count(), 2); community } diff --git a/crates/apub/src/protocol/collections/group_followers.rs b/crates/apub/src/protocol/collections/group_followers.rs index a3814501c..cae55d6df 100644 --- a/crates/apub/src/protocol/collections/group_followers.rs +++ b/crates/apub/src/protocol/collections/group_followers.rs @@ -1,34 +1,12 @@ use activitypub_federation::kinds::collection::CollectionType; -use lemmy_api_common::{context::LemmyContext, utils::generate_followers_url}; -use lemmy_db_schema::source::community::Community; -use lemmy_db_views_actor::structs::CommunityFollowerView; -use lemmy_utils::error::LemmyError; use serde::{Deserialize, Serialize}; use url::Url; #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub(crate) struct GroupFollowers { - id: Url, - r#type: CollectionType, - total_items: i32, - items: Vec<()>, -} - -impl GroupFollowers { - pub(crate) async fn new( - community: Community, - context: &LemmyContext, - ) -> Result { - let community_id = community.id; - let community_followers = - CommunityFollowerView::count_community_followers(&mut context.pool(), community_id).await?; - - Ok(GroupFollowers { - id: generate_followers_url(&community.actor_id)?.into(), - r#type: CollectionType::Collection, - total_items: community_followers as i32, - items: vec![], - }) - } + pub(crate) id: Url, + pub(crate) r#type: CollectionType, + pub(crate) total_items: i32, + pub(crate) items: Vec<()>, } diff --git a/crates/apub/src/protocol/objects/group.rs b/crates/apub/src/protocol/objects/group.rs index ab14ef6ea..8346e85b7 100644 --- a/crates/apub/src/protocol/objects/group.rs +++ b/crates/apub/src/protocol/objects/group.rs @@ -2,6 +2,7 @@ use crate::{ check_apub_id_valid_with_strictness, collections::{ community_featured::ApubCommunityFeatured, + community_follower::ApubCommunityFollower, community_moderators::ApubCommunityModerators, community_outbox::ApubCommunityOutbox, }, @@ -48,7 +49,7 @@ pub struct Group { /// username, set at account creation and usually fixed after that pub(crate) preferred_username: String, pub(crate) inbox: Url, - pub(crate) followers: Url, + pub(crate) followers: CollectionId, pub(crate) public_key: PublicKey, /// title diff --git a/crates/db_schema/src/aggregates/community_aggregates.rs b/crates/db_schema/src/aggregates/community_aggregates.rs index 1cd23e03f..e968fdb08 100644 --- a/crates/db_schema/src/aggregates/community_aggregates.rs +++ b/crates/db_schema/src/aggregates/community_aggregates.rs @@ -1,20 +1,36 @@ use crate::{ aggregates::structs::CommunityAggregates, newtypes::CommunityId, - schema::community_aggregates, + schema::{ + community_aggregates, + community_aggregates::{community_id, subscribers}, + }, utils::{get_conn, DbPool}, }; use diesel::{result::Error, ExpressionMethods, QueryDsl}; use diesel_async::RunQueryDsl; impl CommunityAggregates { - pub async fn read(pool: &mut DbPool<'_>, community_id: CommunityId) -> Result { + pub async fn read(pool: &mut DbPool<'_>, for_community_id: CommunityId) -> Result { let conn = &mut get_conn(pool).await?; community_aggregates::table - .filter(community_aggregates::community_id.eq(community_id)) + .filter(community_id.eq(for_community_id)) .first::(conn) .await } + + pub async fn update_federated_followers( + pool: &mut DbPool<'_>, + for_community_id: CommunityId, + new_subscribers: i32, + ) -> Result { + let conn = &mut get_conn(pool).await?; + let new_subscribers: i64 = new_subscribers.into(); + diesel::update(community_aggregates::table.filter(community_id.eq(for_community_id))) + .set(subscribers.eq(new_subscribers)) + .get_result::(conn) + .await + } } #[cfg(test)] diff --git a/migrations/2023-10-02-145002_community_followers_count_federated/down.sql b/migrations/2023-10-02-145002_community_followers_count_federated/down.sql new file mode 100644 index 000000000..b441d6dda --- /dev/null +++ b/migrations/2023-10-02-145002_community_followers_count_federated/down.sql @@ -0,0 +1,24 @@ +CREATE OR REPLACE FUNCTION community_aggregates_subscriber_count () + RETURNS TRIGGER + LANGUAGE plpgsql + AS $$ +BEGIN + IF (TG_OP = 'INSERT') THEN + UPDATE + community_aggregates + SET + subscribers = subscribers + 1 + WHERE + community_id = NEW.community_id; + ELSIF (TG_OP = 'DELETE') THEN + UPDATE + community_aggregates + SET + subscribers = subscribers - 1 + WHERE + community_id = OLD.community_id; + END IF; + RETURN NULL; +END +$$; + diff --git a/migrations/2023-10-02-145002_community_followers_count_federated/up.sql b/migrations/2023-10-02-145002_community_followers_count_federated/up.sql new file mode 100644 index 000000000..bb99eb53c --- /dev/null +++ b/migrations/2023-10-02-145002_community_followers_count_federated/up.sql @@ -0,0 +1,34 @@ +-- The subscriber count should only be updated for local communities. For remote +-- communities it is read over federation from the origin instance. +CREATE OR REPLACE FUNCTION community_aggregates_subscriber_count () + RETURNS TRIGGER + LANGUAGE plpgsql + AS $$ +BEGIN + IF (TG_OP = 'INSERT') THEN + UPDATE + community_aggregates + SET + subscribers = subscribers + 1 + FROM + community + WHERE + community.id = community_id + AND community.local + AND community_id = NEW.community_id; + ELSIF (TG_OP = 'DELETE') THEN + UPDATE + community_aggregates + SET + subscribers = subscribers - 1 + FROM + community + WHERE + community.id = community_id + AND community.local + AND community_id = OLD.community_id; + END IF; + RETURN NULL; +END +$$; + From cf1c32d2abe5b4083bfd413e369053f0bcbff8c8 Mon Sep 17 00:00:00 2001 From: SleeplessOne1917 Date: Tue, 17 Oct 2023 14:52:34 +0000 Subject: [PATCH 12/24] Enable animated avatars setting (#4040) * feat: Add user setting for toggling avatar gif animations * Add forgotten post listing mode option to settings form * Fix compilation error * Fix formatting * Change name of column to be more general purpose * Fix sql format. * Fix comments * Running format. --------- Co-authored-by: Dessalines Co-authored-by: Dessalines --- crates/api/src/local_user/save_settings.rs | 3 +++ crates/api_common/src/person.rs | 4 ++++ crates/db_schema/src/schema.rs | 1 + crates/db_schema/src/source/local_user.rs | 4 ++++ crates/db_views/src/registration_application_view.rs | 1 + crates/utils/translations | 2 +- migrations/2023-10-13-175712_allow_animated_avatars/down.sql | 3 +++ migrations/2023-10-13-175712_allow_animated_avatars/up.sql | 3 +++ 8 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 migrations/2023-10-13-175712_allow_animated_avatars/down.sql create mode 100644 migrations/2023-10-13-175712_allow_animated_avatars/up.sql diff --git a/crates/api/src/local_user/save_settings.rs b/crates/api/src/local_user/save_settings.rs index d219416f8..90f6b6dd8 100644 --- a/crates/api/src/local_user/save_settings.rs +++ b/crates/api/src/local_user/save_settings.rs @@ -114,6 +114,9 @@ pub async fn save_user_settings( interface_language: data.interface_language.clone(), open_links_in_new_tab: data.open_links_in_new_tab, infinite_scroll_enabled: data.infinite_scroll_enabled, + post_listing_mode: data.post_listing_mode, + enable_keyboard_navigation: data.enable_keyboard_navigation, + enable_animated_images: data.enable_animated_images, ..Default::default() }; diff --git a/crates/api_common/src/person.rs b/crates/api_common/src/person.rs index c410cbbe8..c067c3799 100644 --- a/crates/api_common/src/person.rs +++ b/crates/api_common/src/person.rs @@ -3,6 +3,7 @@ use lemmy_db_schema::{ newtypes::{CommentReplyId, CommunityId, LanguageId, PersonId, PersonMentionId}, CommentSortType, ListingType, + PostListingMode, SortType, }; use lemmy_db_views::structs::{CommentView, PostView}; @@ -123,8 +124,11 @@ pub struct SaveUserSettings { pub open_links_in_new_tab: Option, /// Enable infinite scroll pub infinite_scroll_enabled: Option, + pub post_listing_mode: Option, /// Whether to allow keyboard navigation (for browsing and interacting with posts and comments). pub enable_keyboard_navigation: Option, + /// Whether user avatars or inline images in the UI that are gifs should be allowed to play or should be paused + pub enable_animated_images: Option, } #[derive(Debug, Serialize, Deserialize, Clone, Default)] diff --git a/crates/db_schema/src/schema.rs b/crates/db_schema/src/schema.rs index 6942fdccd..2d6f221fc 100644 --- a/crates/db_schema/src/schema.rs +++ b/crates/db_schema/src/schema.rs @@ -444,6 +444,7 @@ diesel::table! { post_listing_mode -> PostListingModeEnum, totp_2fa_enabled -> Bool, enable_keyboard_navigation -> Bool, + enable_animated_images -> Bool, } } diff --git a/crates/db_schema/src/source/local_user.rs b/crates/db_schema/src/source/local_user.rs index 220593698..08f78bbb8 100644 --- a/crates/db_schema/src/source/local_user.rs +++ b/crates/db_schema/src/source/local_user.rs @@ -58,6 +58,8 @@ pub struct LocalUser { pub totp_2fa_enabled: bool, /// Whether to allow keyboard navigation (for browsing and interacting with posts and comments). pub enable_keyboard_navigation: bool, + /// Whether user avatars and inline images in the UI that are gifs should be allowed to play or should be paused + pub enable_animated_images: bool, } #[derive(Clone, TypedBuilder)] @@ -91,6 +93,7 @@ pub struct LocalUserInsertForm { pub post_listing_mode: Option, pub totp_2fa_enabled: Option, pub enable_keyboard_navigation: Option, + pub enable_animated_images: Option, } #[derive(Clone, Default)] @@ -120,4 +123,5 @@ pub struct LocalUserUpdateForm { pub post_listing_mode: Option, pub totp_2fa_enabled: Option, pub enable_keyboard_navigation: Option, + pub enable_animated_images: Option, } diff --git a/crates/db_views/src/registration_application_view.rs b/crates/db_views/src/registration_application_view.rs index c2d49207a..0bdd50cf1 100644 --- a/crates/db_views/src/registration_application_view.rs +++ b/crates/db_views/src/registration_application_view.rs @@ -267,6 +267,7 @@ mod tests { post_listing_mode: inserted_sara_local_user.post_listing_mode, totp_2fa_enabled: inserted_sara_local_user.totp_2fa_enabled, enable_keyboard_navigation: inserted_sara_local_user.enable_keyboard_navigation, + enable_animated_images: inserted_sara_local_user.enable_animated_images, }, creator: Person { id: inserted_sara_person.id, diff --git a/crates/utils/translations b/crates/utils/translations index e943f97fe..d0f354837 160000 --- a/crates/utils/translations +++ b/crates/utils/translations @@ -1 +1 @@ -Subproject commit e943f97fe481dc425acdebc8872bf1fdcabaf875 +Subproject commit d0f3548379e446d2c333e582734bc68f8d684f4d diff --git a/migrations/2023-10-13-175712_allow_animated_avatars/down.sql b/migrations/2023-10-13-175712_allow_animated_avatars/down.sql new file mode 100644 index 000000000..43543c9bf --- /dev/null +++ b/migrations/2023-10-13-175712_allow_animated_avatars/down.sql @@ -0,0 +1,3 @@ +ALTER TABLE local_user + DROP COLUMN enable_animated_images; + diff --git a/migrations/2023-10-13-175712_allow_animated_avatars/up.sql b/migrations/2023-10-13-175712_allow_animated_avatars/up.sql new file mode 100644 index 000000000..d36d22994 --- /dev/null +++ b/migrations/2023-10-13-175712_allow_animated_avatars/up.sql @@ -0,0 +1,3 @@ +ALTER TABLE local_user + ADD COLUMN enable_animated_images boolean DEFAULT TRUE NOT NULL; + From 3f621350837918b3e2463f060d4e330eb02c88c2 Mon Sep 17 00:00:00 2001 From: Nutomic Date: Tue, 17 Oct 2023 17:25:48 +0200 Subject: [PATCH 13/24] Add validate_auth api endpoint (fixes #3702) (#4049) * Add validate_auth api endpoint (fixes #3702) * clippy --------- Co-authored-by: Dessalines --- crates/api/src/lib.rs | 22 +++++++++++++++++-- crates/api/src/local_user/mod.rs | 1 + crates/api/src/local_user/validate_auth.rs | 23 ++++++++++++++++++++ src/api_routes_http.rs | 4 +++- src/session_middleware.rs | 25 +++------------------- 5 files changed, 50 insertions(+), 25 deletions(-) create mode 100644 crates/api/src/local_user/validate_auth.rs diff --git a/crates/api/src/lib.rs b/crates/api/src/lib.rs index 300b89ffe..5621fe2df 100644 --- a/crates/api/src/lib.rs +++ b/crates/api/src/lib.rs @@ -2,11 +2,15 @@ use actix_web::{http::header::Header, HttpRequest}; use actix_web_httpauth::headers::authorization::{Authorization, Bearer}; use base64::{engine::general_purpose::STANDARD_NO_PAD as base64, Engine}; use captcha::Captcha; -use lemmy_api_common::utils::{local_site_to_slur_regex, AUTH_COOKIE_NAME}; +use lemmy_api_common::{ + claims::Claims, + context::LemmyContext, + utils::{check_user_valid, local_site_to_slur_regex, AUTH_COOKIE_NAME}, +}; use lemmy_db_schema::source::local_site::LocalSite; use lemmy_db_views::structs::LocalUserView; use lemmy_utils::{ - error::{LemmyError, LemmyErrorExt, LemmyErrorType, LemmyResult}, + error::{LemmyError, LemmyErrorExt, LemmyErrorExt2, LemmyErrorType, LemmyResult}, utils::slurs::check_slurs, }; use std::io::Cursor; @@ -144,6 +148,20 @@ pub(crate) fn build_totp_2fa( .with_lemmy_type(LemmyErrorType::CouldntGenerateTotp) } +#[tracing::instrument(skip_all)] +pub async fn local_user_view_from_jwt( + jwt: &str, + context: &LemmyContext, +) -> Result { + let local_user_id = Claims::validate(jwt, context) + .await + .with_lemmy_type(LemmyErrorType::NotLoggedIn)?; + let local_user_view = LocalUserView::read(&mut context.pool(), local_user_id).await?; + check_user_valid(&local_user_view.person)?; + + Ok(local_user_view) +} + #[cfg(test)] mod tests { #![allow(clippy::unwrap_used)] diff --git a/crates/api/src/local_user/mod.rs b/crates/api/src/local_user/mod.rs index 1b58713f1..98e023fa5 100644 --- a/crates/api/src/local_user/mod.rs +++ b/crates/api/src/local_user/mod.rs @@ -14,4 +14,5 @@ pub mod report_count; pub mod reset_password; pub mod save_settings; pub mod update_totp; +pub mod validate_auth; pub mod verify_email; diff --git a/crates/api/src/local_user/validate_auth.rs b/crates/api/src/local_user/validate_auth.rs new file mode 100644 index 000000000..d95195dc9 --- /dev/null +++ b/crates/api/src/local_user/validate_auth.rs @@ -0,0 +1,23 @@ +use crate::{local_user_view_from_jwt, read_auth_token}; +use actix_web::{ + web::{Data, Json}, + HttpRequest, +}; +use lemmy_api_common::{context::LemmyContext, SuccessResponse}; +use lemmy_utils::error::{LemmyError, LemmyErrorType}; + +/// Returns an error message if the auth token is invalid for any reason. Necessary because other +/// endpoints silently treat any call with invalid auth as unauthenticated. +#[tracing::instrument(skip(context))] +pub async fn validate_auth( + req: HttpRequest, + context: Data, +) -> Result, LemmyError> { + let jwt = read_auth_token(&req)?; + if let Some(jwt) = jwt { + local_user_view_from_jwt(&jwt, &context).await?; + } else { + Err(LemmyErrorType::NotLoggedIn)?; + } + Ok(Json(SuccessResponse::default())) +} diff --git a/src/api_routes_http.rs b/src/api_routes_http.rs index 3546b3400..26d6b9e8d 100644 --- a/src/api_routes_http.rs +++ b/src/api_routes_http.rs @@ -38,6 +38,7 @@ use lemmy_api::{ reset_password::reset_password, save_settings::save_user_settings, update_totp::update_totp, + validate_auth::validate_auth, verify_email::verify_email, }, post::{ @@ -296,7 +297,8 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimitCell) { .route("/leave_admin", web::post().to(leave_admin)) .route("/totp/generate", web::post().to(generate_totp_secret)) .route("/totp/update", web::post().to(update_totp)) - .route("/list_logins", web::get().to(list_logins)), + .route("/list_logins", web::get().to(list_logins)) + .route("/validate_auth", web::get().to(validate_auth)), ) .service( web::scope("/user") diff --git a/src/session_middleware.rs b/src/session_middleware.rs index 80b79f917..ae82cd44d 100644 --- a/src/session_middleware.rs +++ b/src/session_middleware.rs @@ -7,14 +7,8 @@ use actix_web::{ }; use core::future::Ready; use futures_util::future::LocalBoxFuture; -use lemmy_api::read_auth_token; -use lemmy_api_common::{ - claims::Claims, - context::LemmyContext, - lemmy_db_views::structs::LocalUserView, - utils::check_user_valid, -}; -use lemmy_utils::error::{LemmyError, LemmyErrorExt2, LemmyErrorType}; +use lemmy_api::{local_user_view_from_jwt, read_auth_token}; +use lemmy_api_common::context::LemmyContext; use reqwest::header::HeaderValue; use std::{future::ready, rc::Rc}; @@ -100,20 +94,6 @@ where } } -#[tracing::instrument(skip_all)] -async fn local_user_view_from_jwt( - jwt: &str, - context: &LemmyContext, -) -> Result { - let local_user_id = Claims::validate(jwt, context) - .await - .with_lemmy_type(LemmyErrorType::NotLoggedIn)?; - let local_user_view = LocalUserView::read(&mut context.pool(), local_user_id).await?; - check_user_valid(&local_user_view.person)?; - - Ok(local_user_view) -} - #[cfg(test)] mod tests { #![allow(clippy::unwrap_used)] @@ -121,6 +101,7 @@ mod tests { use super::*; use actix_web::test::TestRequest; + use lemmy_api_common::claims::Claims; use lemmy_db_schema::{ source::{ instance::Instance, From 3a19af52159f6a4f2e41094cefdf271ce208c833 Mon Sep 17 00:00:00 2001 From: Nutomic Date: Tue, 17 Oct 2023 18:35:51 +0200 Subject: [PATCH 14/24] Allow marking multiple posts as read in single api call (fixes #3963) (#4048) * Allow marking multiple posts as read in single api call (fixes #3963) * cleanup * limit array length * fix test * review --------- Co-authored-by: Dessalines --- crates/api/src/post/mark_read.rs | 34 ++++---- crates/api_common/src/post.rs | 2 + crates/api_common/src/utils.rs | 27 ++---- crates/apub/src/api/user_settings_backup.rs | 14 +-- crates/db_schema/src/impls/post.rs | 94 ++++++++++++--------- crates/db_schema/src/source/post.rs | 2 +- crates/db_schema/src/traits.rs | 11 --- crates/utils/src/error.rs | 6 +- 8 files changed, 91 insertions(+), 99 deletions(-) diff --git a/crates/api/src/post/mark_read.rs b/crates/api/src/post/mark_read.rs index a248b0196..a377b3c6c 100644 --- a/crates/api/src/post/mark_read.rs +++ b/crates/api/src/post/mark_read.rs @@ -1,30 +1,34 @@ use actix_web::web::{Data, Json}; -use lemmy_api_common::{ - context::LemmyContext, - post::{MarkPostAsRead, PostResponse}, - utils, -}; -use lemmy_db_views::structs::{LocalUserView, PostView}; -use lemmy_utils::error::LemmyError; +use lemmy_api_common::{context::LemmyContext, post::MarkPostAsRead, SuccessResponse}; +use lemmy_db_schema::source::post::PostRead; +use lemmy_db_views::structs::LocalUserView; +use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType, MAX_API_PARAM_ELEMENTS}; +use std::collections::HashSet; #[tracing::instrument(skip(context))] pub async fn mark_post_as_read( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { - let post_id = data.post_id; +) -> Result, LemmyError> { + let mut post_ids = data.post_ids.iter().cloned().collect::>(); + post_ids.insert(data.post_id); let person_id = local_user_view.person.id; + if post_ids.len() > MAX_API_PARAM_ELEMENTS { + Err(LemmyErrorType::TooManyItems)?; + } + // Mark the post as read / unread if data.read { - utils::mark_post_as_read(person_id, post_id, &mut context.pool()).await?; + PostRead::mark_as_read(&mut context.pool(), post_ids, person_id) + .await + .with_lemmy_type(LemmyErrorType::CouldntMarkPostAsRead)?; } else { - utils::mark_post_as_unread(person_id, post_id, &mut context.pool()).await?; + PostRead::mark_as_unread(&mut context.pool(), post_ids, person_id) + .await + .with_lemmy_type(LemmyErrorType::CouldntMarkPostAsRead)?; } - // Fetch it - let post_view = PostView::read(&mut context.pool(), post_id, Some(person_id), false).await?; - - Ok(Json(PostResponse { post_view })) + Ok(Json(SuccessResponse::default())) } diff --git a/crates/api_common/src/post.rs b/crates/api_common/src/post.rs index 86f721701..fbb6f1d38 100644 --- a/crates/api_common/src/post.rs +++ b/crates/api_common/src/post.rs @@ -140,7 +140,9 @@ pub struct RemovePost { #[cfg_attr(feature = "full", ts(export))] /// Mark a post as read. pub struct MarkPostAsRead { + /// TODO: deprecated, send `post_ids` instead pub post_id: PostId, + pub post_ids: Vec, pub read: bool, } diff --git a/crates/api_common/src/utils.rs b/crates/api_common/src/utils.rs index 139620b67..df84adc16 100644 --- a/crates/api_common/src/utils.rs +++ b/crates/api_common/src/utils.rs @@ -18,9 +18,9 @@ use lemmy_db_schema::{ password_reset_request::PasswordResetRequest, person::{Person, PersonUpdateForm}, person_block::PersonBlock, - post::{Post, PostRead, PostReadForm}, + post::{Post, PostRead}, }, - traits::{Crud, Readable}, + traits::Crud, utils::DbPool, }; use lemmy_db_views::{comment_view::CommentQuery, structs::LocalUserView}; @@ -39,6 +39,7 @@ use lemmy_utils::{ }; use regex::Regex; use rosetta_i18n::{Language, LanguageId}; +use std::collections::HashSet; use tracing::warn; use url::{ParseError, Url}; @@ -117,25 +118,11 @@ pub async fn mark_post_as_read( person_id: PersonId, post_id: PostId, pool: &mut DbPool<'_>, -) -> Result { - let post_read_form = PostReadForm { post_id, person_id }; - - PostRead::mark_as_read(pool, &post_read_form) +) -> Result<(), LemmyError> { + PostRead::mark_as_read(pool, HashSet::from([post_id]), person_id) .await - .with_lemmy_type(LemmyErrorType::CouldntMarkPostAsRead) -} - -#[tracing::instrument(skip_all)] -pub async fn mark_post_as_unread( - person_id: PersonId, - post_id: PostId, - pool: &mut DbPool<'_>, -) -> Result { - let post_read_form = PostReadForm { post_id, person_id }; - - PostRead::mark_as_unread(pool, &post_read_form) - .await - .with_lemmy_type(LemmyErrorType::CouldntMarkPostAsRead) + .with_lemmy_type(LemmyErrorType::CouldntMarkPostAsRead)?; + Ok(()) } pub fn check_user_valid(person: &Person) -> Result<(), LemmyError> { diff --git a/crates/apub/src/api/user_settings_backup.rs b/crates/apub/src/api/user_settings_backup.rs index 3a9b05847..419be280d 100644 --- a/crates/apub/src/api/user_settings_backup.rs +++ b/crates/apub/src/api/user_settings_backup.rs @@ -23,18 +23,12 @@ use lemmy_db_schema::{ }; use lemmy_db_views::structs::LocalUserView; use lemmy_utils::{ - error::{LemmyError, LemmyErrorType, LemmyResult}, + error::{LemmyError, LemmyErrorType, LemmyResult, MAX_API_PARAM_ELEMENTS}, spawn_try_task, }; use serde::{Deserialize, Serialize}; use tracing::info; -/// Maximum number of follow/block URLs which can be imported at once, to prevent server overloading. -/// To import a larger backup, split it into multiple parts. -/// -/// TODO: having the user manually split files will very be confusing -const MAX_URL_IMPORT_COUNT: usize = 1000; - /// Backup of user data. This struct should never be changed so that the data can be used as a /// long-term backup in case the instance goes down unexpectedly. All fields are optional to allow /// importing partial backups. @@ -138,8 +132,8 @@ pub async fn import_settings( + data.blocked_users.len() + data.saved_posts.len() + data.saved_comments.len(); - if url_count > MAX_URL_IMPORT_COUNT { - Err(LemmyErrorType::UserBackupTooLarge)?; + if url_count > MAX_API_PARAM_ELEMENTS { + Err(LemmyErrorType::TooManyItems)?; } spawn_try_task(async move { @@ -434,7 +428,7 @@ mod tests { assert_eq!( imported.err().unwrap().error_type, - LemmyErrorType::UserBackupTooLarge + LemmyErrorType::TooManyItems ); LocalUser::delete(&mut context.pool(), export_user.local_user.id) diff --git a/crates/db_schema/src/impls/post.rs b/crates/db_schema/src/impls/post.rs index 514aceaba..4a719415a 100644 --- a/crates/db_schema/src/impls/post.rs +++ b/crates/db_schema/src/impls/post.rs @@ -28,13 +28,14 @@ use crate::{ PostSavedForm, PostUpdateForm, }, - traits::{Crud, Likeable, Readable, Saveable}, + traits::{Crud, Likeable, Saveable}, utils::{get_conn, naive_now, DbPool, DELETED_REPLACEMENT_TEXT, FETCH_LIMIT_MAX}, }; use ::url::Url; use chrono::{Duration, Utc}; use diesel::{dsl::insert_into, result::Error, ExpressionMethods, QueryDsl, TextExpressionMethods}; use diesel_async::RunQueryDsl; +use std::collections::HashSet; #[async_trait] impl Crud for Post { @@ -302,34 +303,38 @@ impl Saveable for PostSaved { } } -#[async_trait] -impl Readable for PostRead { - type Form = PostReadForm; - async fn mark_as_read( +impl PostRead { + pub async fn mark_as_read( pool: &mut DbPool<'_>, - post_read_form: &PostReadForm, - ) -> Result { - use crate::schema::post_read::dsl::{person_id, post_id, post_read}; + post_ids: HashSet, + person_id: PersonId, + ) -> Result { + use crate::schema::post_read::dsl::post_read; let conn = &mut get_conn(pool).await?; + + let forms = post_ids + .into_iter() + .map(|post_id| PostReadForm { post_id, person_id }) + .collect::>(); insert_into(post_read) - .values(post_read_form) - .on_conflict((post_id, person_id)) - .do_update() - .set(post_read_form) - .get_result::(conn) + .values(forms) + .on_conflict_do_nothing() + .execute(conn) .await } - async fn mark_as_unread( + pub async fn mark_as_unread( pool: &mut DbPool<'_>, - post_read_form: &PostReadForm, + post_id_: HashSet, + person_id_: PersonId, ) -> Result { use crate::schema::post_read::dsl::{person_id, post_id, post_read}; let conn = &mut get_conn(pool).await?; + diesel::delete( post_read - .filter(post_id.eq(post_read_form.post_id)) - .filter(person_id.eq(post_read_form.person_id)), + .filter(post_id.eq_any(post_id_)) + .filter(person_id.eq(person_id_)), ) .execute(conn) .await @@ -352,16 +357,16 @@ mod tests { PostLike, PostLikeForm, PostRead, - PostReadForm, PostSaved, PostSavedForm, PostUpdateForm, }, }, - traits::{Crud, Likeable, Readable, Saveable}, + traits::{Crud, Likeable, Saveable}, utils::build_db_pool_for_tests, }; use serial_test::serial; + use std::collections::HashSet; #[tokio::test] #[serial] @@ -398,6 +403,13 @@ mod tests { let inserted_post = Post::create(pool, &new_post).await.unwrap(); + let new_post2 = PostInsertForm::builder() + .name("A test post 2".into()) + .creator_id(inserted_person.id) + .community_id(inserted_community.id) + .build(); + let inserted_post2 = Post::create(pool, &new_post2).await.unwrap(); + let expected_post = Post { id: inserted_post.id, name: "A test post".into(), @@ -455,19 +467,14 @@ mod tests { }; // Post Read - let post_read_form = PostReadForm { - post_id: inserted_post.id, - person_id: inserted_person.id, - }; - - let inserted_post_read = PostRead::mark_as_read(pool, &post_read_form).await.unwrap(); - - let expected_post_read = PostRead { - id: inserted_post_read.id, - post_id: inserted_post.id, - person_id: inserted_person.id, - published: inserted_post_read.published, - }; + let marked_as_read = PostRead::mark_as_read( + pool, + HashSet::from([inserted_post.id, inserted_post2.id]), + inserted_person.id, + ) + .await + .unwrap(); + assert_eq!(2, marked_as_read); let read_post = Post::read(pool, inserted_post.id).await.unwrap(); @@ -482,11 +489,21 @@ mod tests { let like_removed = PostLike::remove(pool, inserted_person.id, inserted_post.id) .await .unwrap(); + assert_eq!(1, like_removed); let saved_removed = PostSaved::unsave(pool, &post_saved_form).await.unwrap(); - let read_removed = PostRead::mark_as_unread(pool, &post_read_form) - .await - .unwrap(); - let num_deleted = Post::delete(pool, inserted_post.id).await.unwrap(); + assert_eq!(1, saved_removed); + let read_removed = PostRead::mark_as_unread( + pool, + HashSet::from([inserted_post.id, inserted_post2.id]), + inserted_person.id, + ) + .await + .unwrap(); + assert_eq!(2, read_removed); + + let num_deleted = Post::delete(pool, inserted_post.id).await.unwrap() + + Post::delete(pool, inserted_post2.id).await.unwrap(); + assert_eq!(2, num_deleted); Community::delete(pool, inserted_community.id) .await .unwrap(); @@ -498,10 +515,5 @@ mod tests { assert_eq!(expected_post, updated_post); assert_eq!(expected_post_like, inserted_post_like); assert_eq!(expected_post_saved, inserted_post_saved); - assert_eq!(expected_post_read, inserted_post_read); - assert_eq!(1, like_removed); - assert_eq!(1, saved_removed); - assert_eq!(1, read_removed); - assert_eq!(1, num_deleted); } } diff --git a/crates/db_schema/src/source/post.rs b/crates/db_schema/src/source/post.rs index 4fe8e34c6..72c32d4af 100644 --- a/crates/db_schema/src/source/post.rs +++ b/crates/db_schema/src/source/post.rs @@ -162,7 +162,7 @@ pub struct PostRead { #[cfg_attr(feature = "full", derive(Insertable, AsChangeset))] #[cfg_attr(feature = "full", diesel(table_name = post_read))] -pub struct PostReadForm { +pub(crate) struct PostReadForm { pub post_id: PostId, pub person_id: PersonId, } diff --git a/crates/db_schema/src/traits.rs b/crates/db_schema/src/traits.rs index 9fa5a598c..e58319c0b 100644 --- a/crates/db_schema/src/traits.rs +++ b/crates/db_schema/src/traits.rs @@ -140,17 +140,6 @@ pub trait Blockable { Self: Sized; } -#[async_trait] -pub trait Readable { - type Form; - async fn mark_as_read(pool: &mut DbPool<'_>, form: &Self::Form) -> Result - where - Self: Sized; - async fn mark_as_unread(pool: &mut DbPool<'_>, form: &Self::Form) -> Result - where - Self: Sized; -} - #[async_trait] pub trait Reportable { type Form; diff --git a/crates/utils/src/error.rs b/crates/utils/src/error.rs index 7ac42f0bb..3b5da1bc9 100644 --- a/crates/utils/src/error.rs +++ b/crates/utils/src/error.rs @@ -15,6 +15,9 @@ pub struct LemmyError { pub context: SpanTrace, } +/// Maximum number of items in an array passed as API parameter. See [[LemmyErrorType::TooManyItems]] +pub const MAX_API_PARAM_ELEMENTS: usize = 1000; + impl From for LemmyError where T: Into, @@ -215,8 +218,9 @@ pub enum LemmyErrorType { InstanceBlockAlreadyExists, /// `jwt` cookie must be marked secure and httponly AuthCookieInsecure, + /// Thrown when an API call is submitted with more than 1000 array elements, see [[MAX_API_PARAM_ELEMENTS]] + TooManyItems, CommunityHasNoFollowers, - UserBackupTooLarge, Unknown(String), } From d827af725a0b44eb1f440763c7d06caa7e094154 Mon Sep 17 00:00:00 2001 From: Nutomic Date: Tue, 17 Oct 2023 19:22:50 +0200 Subject: [PATCH 15/24] List distinguished comments first (fixes #3843) (#4050) * List distinguished comments first (fixes #3843) * then_order_by * Fixing assert. * move line * then --------- Co-authored-by: Dessalines --- crates/db_views/src/comment_view.rs | 39 ++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/crates/db_views/src/comment_view.rs b/crates/db_views/src/comment_view.rs index 66b52411d..98536fb8f 100644 --- a/crates/db_views/src/comment_view.rs +++ b/crates/db_views/src/comment_view.rs @@ -234,7 +234,7 @@ fn queries<'a>() -> Queries< // only order if filtering by a post id, or parent_path. DOS potential otherwise and max_depth + !post_id isn't used anyways (afaik) if options.post_id.is_some() || options.parent_path.is_some() { // Always order by the parent path first - query = query.order_by(subpath(comment::path, 0, -1)); + query = query.then_order_by(subpath(comment::path, 0, -1)); } // TODO limit question. Limiting does not work for comment threads ATM, only max_depth @@ -253,6 +253,11 @@ fn queries<'a>() -> Queries< limit_and_offset(options.page, options.limit)? }; + // distinguished comments should go first when viewing post + if options.post_id.is_some() || options.parent_path.is_some() { + query = query.then_order_by(comment::distinguished.desc()); + } + query = match options.sort.unwrap_or(CommentSortType::Hot) { CommentSortType::Hot => query .then_order_by(comment_aggregates::hot_rank.desc()) @@ -262,7 +267,7 @@ fn queries<'a>() -> Queries< } CommentSortType::New => query.then_order_by(comment::published.desc()), CommentSortType::Old => query.then_order_by(comment::published.asc()), - CommentSortType::Top => query.order_by(comment_aggregates::score.desc()), + CommentSortType::Top => query.then_order_by(comment_aggregates::score.desc()), }; // Note: deleted and removed comments are done on the front side @@ -332,7 +337,7 @@ mod tests { newtypes::LanguageId, source::{ actor_language::LocalUserLanguage, - comment::{Comment, CommentInsertForm, CommentLike, CommentLikeForm}, + comment::{Comment, CommentInsertForm, CommentLike, CommentLikeForm, CommentUpdateForm}, community::{Community, CommunityInsertForm}, instance::Instance, language::Language, @@ -746,6 +751,34 @@ mod tests { cleanup(data, pool).await; } + #[tokio::test] + #[serial] + async fn test_distinguished_first() { + let pool = &build_db_pool_for_tests().await; + let pool = &mut pool.into(); + let data = init_data(pool).await; + + let form = CommentUpdateForm { + distinguished: Some(true), + ..Default::default() + }; + Comment::update(pool, data.inserted_comment_2.id, &form) + .await + .unwrap(); + + let comments = CommentQuery { + post_id: Some(data.inserted_comment_2.post_id), + ..Default::default() + } + .list(pool) + .await + .unwrap(); + assert_eq!(comments[0].comment.id, data.inserted_comment_2.id); + assert!(comments[0].comment.distinguished); + + cleanup(data, pool).await; + } + async fn cleanup(data: Data, pool: &mut DbPool<'_>) { CommentLike::remove( pool, From 6d27bfed0800c90c95fa5186c7b6bfd7b1f057d1 Mon Sep 17 00:00:00 2001 From: Nutomic Date: Tue, 17 Oct 2023 19:25:35 +0200 Subject: [PATCH 16/24] Handle invalid ban expires values (fixes #4045) (#4046) * Handle invalid ban expires values (fixes #4045) * Adding a few missing expire time checks. Fixing up time conversions. (#4051) * Adding a few missing expire time checks. Fixing up time conversions. * Increase settings export wait time. * get rid of RemoveCommunity.expires * fmt * tests --------- Co-authored-by: Dessalines --- crates/api/src/community/ban.rs | 6 +-- crates/api/src/local_user/ban_person.rs | 7 +-- crates/api_common/src/community.rs | 1 - crates/api_common/src/utils.rs | 52 ++++++++++++++++++- crates/api_crud/src/community/remove.rs | 7 +-- crates/apub/src/activities/block/mod.rs | 16 +++--- crates/apub/src/activities/deletion/delete.rs | 1 - .../src/activities/deletion/undo_delete.rs | 1 - crates/apub/src/objects/comment.rs | 6 +-- crates/apub/src/objects/community.rs | 9 ++-- crates/apub/src/objects/instance.rs | 5 +- crates/apub/src/objects/person.rs | 5 +- crates/apub/src/objects/post.rs | 5 +- crates/apub/src/objects/private_message.rs | 6 +-- crates/db_schema/src/impls/moderator.rs | 2 - crates/db_schema/src/schema.rs | 1 - crates/db_schema/src/source/moderator.rs | 2 - crates/utils/src/error.rs | 2 + crates/utils/src/utils/mod.rs | 1 - crates/utils/src/utils/time.rs | 12 ----- .../down.sql | 3 ++ .../up.sql | 3 ++ 22 files changed, 91 insertions(+), 62 deletions(-) delete mode 100644 crates/utils/src/utils/time.rs create mode 100644 migrations/2023-10-17-181800_drop_remove_community_expires/down.sql create mode 100644 migrations/2023-10-17-181800_drop_remove_community_expires/up.sql diff --git a/crates/api/src/community/ban.rs b/crates/api/src/community/ban.rs index 8e9aedbad..f662c4a08 100644 --- a/crates/api/src/community/ban.rs +++ b/crates/api/src/community/ban.rs @@ -4,7 +4,7 @@ use lemmy_api_common::{ community::{BanFromCommunity, BanFromCommunityResponse}, context::LemmyContext, send_activity::{ActivityChannel, SendActivityData}, - utils::{check_community_mod_action, remove_user_data_in_community}, + utils::{check_community_mod_action, check_expire_time, remove_user_data_in_community}, }; use lemmy_db_schema::{ source::{ @@ -22,7 +22,7 @@ use lemmy_db_views::structs::LocalUserView; use lemmy_db_views_actor::structs::PersonView; use lemmy_utils::{ error::{LemmyError, LemmyErrorExt, LemmyErrorType}, - utils::{time::naive_from_unix, validation::is_valid_body_field}, + utils::validation::is_valid_body_field, }; #[tracing::instrument(skip(context))] @@ -33,7 +33,7 @@ pub async fn ban_from_community( ) -> Result, LemmyError> { let banned_person_id = data.person_id; let remove_data = data.remove_data.unwrap_or(false); - let expires = data.expires.map(naive_from_unix); + let expires = check_expire_time(data.expires)?; // Verify that only mods or admins can ban check_community_mod_action( diff --git a/crates/api/src/local_user/ban_person.rs b/crates/api/src/local_user/ban_person.rs index 8ff203f0e..d7c47e619 100644 --- a/crates/api/src/local_user/ban_person.rs +++ b/crates/api/src/local_user/ban_person.rs @@ -4,7 +4,7 @@ use lemmy_api_common::{ context::LemmyContext, person::{BanPerson, BanPersonResponse}, send_activity::{ActivityChannel, SendActivityData}, - utils::{is_admin, remove_user_data}, + utils::{check_expire_time, is_admin, remove_user_data}, }; use lemmy_db_schema::{ source::{ @@ -18,8 +18,9 @@ use lemmy_db_views::structs::LocalUserView; use lemmy_db_views_actor::structs::PersonView; use lemmy_utils::{ error::{LemmyError, LemmyErrorExt, LemmyErrorType}, - utils::{time::naive_from_unix, validation::is_valid_body_field}, + utils::validation::is_valid_body_field, }; + #[tracing::instrument(skip(context))] pub async fn ban_from_site( data: Json, @@ -31,7 +32,7 @@ pub async fn ban_from_site( is_valid_body_field(&data.reason, false)?; - let expires = data.expires.map(naive_from_unix); + let expires = check_expire_time(data.expires)?; let person = Person::update( &mut context.pool(), diff --git a/crates/api_common/src/community.rs b/crates/api_common/src/community.rs index 7c96344b5..8e87ab750 100644 --- a/crates/api_common/src/community.rs +++ b/crates/api_common/src/community.rs @@ -180,7 +180,6 @@ pub struct RemoveCommunity { pub community_id: CommunityId, pub removed: bool, pub reason: Option, - pub expires: Option, } #[derive(Debug, Serialize, Deserialize, Clone, Default)] diff --git a/crates/api_common/src/utils.rs b/crates/api_common/src/utils.rs index df84adc16..b3dcd7558 100644 --- a/crates/api_common/src/utils.rs +++ b/crates/api_common/src/utils.rs @@ -6,6 +6,7 @@ use crate::{ }; use actix_web::cookie::{Cookie, SameSite}; use anyhow::Context; +use chrono::{DateTime, Days, Local, TimeZone, Utc}; use lemmy_db_schema::{ newtypes::{CommunityId, DbUrl, PersonId, PostId}, source::{ @@ -761,12 +762,40 @@ pub fn create_login_cookie(jwt: Sensitive) -> Cookie<'static> { cookie } +/// Ensure that ban/block expiry is in valid range. If its in past, throw error. If its more +/// than 10 years in future, convert to permanent ban. Otherwise return the same value. +pub fn check_expire_time(expires_unix_opt: Option) -> LemmyResult>> { + if let Some(expires_unix) = expires_unix_opt { + let expires = Utc + .timestamp_opt(expires_unix, 0) + .single() + .ok_or(LemmyErrorType::InvalidUnixTime)?; + + limit_expire_time(expires) + } else { + Ok(None) + } +} + +fn limit_expire_time(expires: DateTime) -> LemmyResult>> { + const MAX_BAN_TERM: Days = Days::new(10 * 365); + + if expires < Local::now() { + Err(LemmyErrorType::BanExpirationInPast)? + } else if expires > Local::now() + MAX_BAN_TERM { + Ok(None) + } else { + Ok(Some(expires)) + } +} + #[cfg(test)] mod tests { #![allow(clippy::unwrap_used)] #![allow(clippy::indexing_slicing)] - use crate::utils::{honeypot_check, password_length_check}; + use crate::utils::{honeypot_check, limit_expire_time, password_length_check}; + use chrono::{Days, Utc}; #[test] #[rustfmt::skip] @@ -784,4 +813,25 @@ mod tests { assert!(honeypot_check(&Some("1".to_string())).is_err()); assert!(honeypot_check(&Some("message".to_string())).is_err()); } + + #[test] + fn test_limit_ban_term() { + // Ban expires in past, should throw error + assert!(limit_expire_time(Utc::now() - Days::new(5)).is_err()); + + // Legitimate ban term, return same value + let fourteen_days = Utc::now() + Days::new(14); + assert_eq!( + limit_expire_time(fourteen_days).unwrap(), + Some(fourteen_days) + ); + let nine_years = Utc::now() + Days::new(365 * 9); + assert_eq!(limit_expire_time(nine_years).unwrap(), Some(nine_years)); + + // Too long ban term, changes to None (permanent ban) + assert_eq!( + limit_expire_time(Utc::now() + Days::new(365 * 11)).unwrap(), + None + ); + } } diff --git a/crates/api_crud/src/community/remove.rs b/crates/api_crud/src/community/remove.rs index 9604b0432..3c21c02b2 100644 --- a/crates/api_crud/src/community/remove.rs +++ b/crates/api_crud/src/community/remove.rs @@ -15,10 +15,7 @@ use lemmy_db_schema::{ traits::Crud, }; use lemmy_db_views::structs::LocalUserView; -use lemmy_utils::{ - error::{LemmyError, LemmyErrorExt, LemmyErrorType}, - utils::time::naive_from_unix, -}; +use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; #[tracing::instrument(skip(context))] pub async fn remove_community( @@ -52,13 +49,11 @@ pub async fn remove_community( .with_lemmy_type(LemmyErrorType::CouldntUpdateCommunity)?; // Mod tables - let expires = data.expires.map(naive_from_unix); let form = ModRemoveCommunityForm { mod_person_id: local_user_view.person.id, community_id: data.community_id, removed: Some(removed), reason: data.reason.clone(), - expires, }; ModRemoveCommunity::create(&mut context.pool(), &form).await?; diff --git a/crates/apub/src/activities/block/mod.rs b/crates/apub/src/activities/block/mod.rs index 0d64aacd4..c6bef9a00 100644 --- a/crates/apub/src/activities/block/mod.rs +++ b/crates/apub/src/activities/block/mod.rs @@ -11,7 +11,12 @@ use activitypub_federation::{ traits::{Actor, Object}, }; use chrono::{DateTime, Utc}; -use lemmy_api_common::{community::BanFromCommunity, context::LemmyContext, person::BanPerson}; +use lemmy_api_common::{ + community::BanFromCommunity, + context::LemmyContext, + person::BanPerson, + utils::check_expire_time, +}; use lemmy_db_schema::{ newtypes::CommunityId, source::{community::Community, person::Person, site::Site}, @@ -19,10 +24,7 @@ use lemmy_db_schema::{ utils::DbPool, }; use lemmy_db_views::structs::SiteView; -use lemmy_utils::{ - error::{LemmyError, LemmyResult}, - utils::time::naive_from_unix, -}; +use lemmy_utils::error::{LemmyError, LemmyResult}; use serde::Deserialize; use url::Url; @@ -137,7 +139,7 @@ pub(crate) async fn send_ban_from_site( context: Data, ) -> Result<(), LemmyError> { let site = SiteOrCommunity::Site(SiteView::read_local(&mut context.pool()).await?.site.into()); - let expires = data.expires.map(naive_from_unix); + let expires = check_expire_time(data.expires)?; // if the action affects a local user, federate to other instances if banned_user.local { @@ -177,7 +179,7 @@ pub(crate) async fn send_ban_from_community( let community: ApubCommunity = Community::read(&mut context.pool(), community_id) .await? .into(); - let expires = data.expires.map(naive_from_unix); + let expires = check_expire_time(data.expires)?; if data.ban { BlockUser::send( diff --git a/crates/apub/src/activities/deletion/delete.rs b/crates/apub/src/activities/deletion/delete.rs index 28c4eace7..140c98665 100644 --- a/crates/apub/src/activities/deletion/delete.rs +++ b/crates/apub/src/activities/deletion/delete.rs @@ -115,7 +115,6 @@ pub(in crate::activities) async fn receive_remove_action( community_id: community.id, removed: Some(true), reason, - expires: None, }; ModRemoveCommunity::create(&mut context.pool(), &form).await?; Community::update( diff --git a/crates/apub/src/activities/deletion/undo_delete.rs b/crates/apub/src/activities/deletion/undo_delete.rs index 6572938dd..697153fc2 100644 --- a/crates/apub/src/activities/deletion/undo_delete.rs +++ b/crates/apub/src/activities/deletion/undo_delete.rs @@ -107,7 +107,6 @@ impl UndoDelete { community_id: community.id, removed: Some(false), reason: None, - expires: None, }; ModRemoveCommunity::create(&mut context.pool(), &form).await?; Community::update( diff --git a/crates/apub/src/objects/comment.rs b/crates/apub/src/objects/comment.rs index 4d57b50ee..ecee70724 100644 --- a/crates/apub/src/objects/comment.rs +++ b/crates/apub/src/objects/comment.rs @@ -29,7 +29,7 @@ use lemmy_db_schema::{ }; use lemmy_utils::{ error::{LemmyError, LemmyErrorType}, - utils::{markdown::markdown_to_html, slurs::remove_slurs, time::convert_datetime}, + utils::{markdown::markdown_to_html, slurs::remove_slurs}, }; use std::ops::Deref; use url::Url; @@ -113,8 +113,8 @@ impl Object for ApubComment { media_type: Some(MediaTypeMarkdownOrHtml::Html), source: Some(Source::new(self.content.clone())), in_reply_to, - published: Some(convert_datetime(self.published)), - updated: self.updated.map(convert_datetime), + published: Some(self.published), + updated: self.updated, tag: maa.tags, distinguished: Some(self.distinguished), language, diff --git a/crates/apub/src/objects/community.rs b/crates/apub/src/objects/community.rs index d88f457bd..69d6231c0 100644 --- a/crates/apub/src/objects/community.rs +++ b/crates/apub/src/objects/community.rs @@ -28,10 +28,7 @@ use lemmy_db_schema::{ traits::{ApubActor, Crud}, }; use lemmy_db_views_actor::structs::CommunityFollowerView; -use lemmy_utils::{ - error::LemmyError, - utils::{markdown::markdown_to_html, time::convert_datetime}, -}; +use lemmy_utils::{error::LemmyError, utils::markdown::markdown_to_html}; use std::ops::Deref; use tracing::debug; use url::Url; @@ -109,8 +106,8 @@ impl Object for ApubCommunity { }), public_key: self.public_key(), language, - published: Some(convert_datetime(self.published)), - updated: self.updated.map(convert_datetime), + published: Some(self.published), + updated: self.updated, posting_restricted_to_mods: Some(self.posting_restricted_to_mods), attributed_to: Some(generate_moderators_url(&self.actor_id)?.into()), }; diff --git a/crates/apub/src/objects/instance.rs b/crates/apub/src/objects/instance.rs index d6086fdc2..3044d77f2 100644 --- a/crates/apub/src/objects/instance.rs +++ b/crates/apub/src/objects/instance.rs @@ -34,7 +34,6 @@ use lemmy_utils::{ utils::{ markdown::markdown_to_html, slurs::{check_slurs, check_slurs_opt}, - time::convert_datetime, }, }; use std::ops::Deref; @@ -103,8 +102,8 @@ impl Object for ApubSite { outbox: Url::parse(&format!("{}/site_outbox", self.actor_id))?, public_key: self.public_key(), language, - published: convert_datetime(self.published), - updated: self.updated.map(convert_datetime), + published: self.published, + updated: self.updated, }; Ok(instance) } diff --git a/crates/apub/src/objects/person.rs b/crates/apub/src/objects/person.rs index 3ca473616..1102567d0 100644 --- a/crates/apub/src/objects/person.rs +++ b/crates/apub/src/objects/person.rs @@ -35,7 +35,6 @@ use lemmy_utils::{ utils::{ markdown::markdown_to_html, slurs::{check_slurs, check_slurs_opt}, - time::convert_datetime, }, }; use std::ops::Deref; @@ -107,13 +106,13 @@ impl Object for ApubPerson { icon: self.avatar.clone().map(ImageObject::new), image: self.banner.clone().map(ImageObject::new), matrix_user_id: self.matrix_user_id.clone(), - published: Some(convert_datetime(self.published)), + published: Some(self.published), outbox: generate_outbox_url(&self.actor_id)?.into(), endpoints: self.shared_inbox_url.clone().map(|s| Endpoints { shared_inbox: s.into(), }), public_key: self.public_key(), - updated: self.updated.map(convert_datetime), + updated: self.updated, inbox: self.inbox_url.clone().into(), }; Ok(person) diff --git a/crates/apub/src/objects/post.rs b/crates/apub/src/objects/post.rs index 6aba17554..a86d4342f 100644 --- a/crates/apub/src/objects/post.rs +++ b/crates/apub/src/objects/post.rs @@ -43,7 +43,6 @@ use lemmy_utils::{ utils::{ markdown::markdown_to_html, slurs::{check_slurs_opt, remove_slurs}, - time::convert_datetime, validation::check_url_scheme, }, }; @@ -127,8 +126,8 @@ impl Object for ApubPost { comments_enabled: Some(!self.locked), sensitive: Some(self.nsfw), language, - published: Some(convert_datetime(self.published)), - updated: self.updated.map(convert_datetime), + published: Some(self.published), + updated: self.updated, audience: Some(community.actor_id.into()), in_reply_to: None, }; diff --git a/crates/apub/src/objects/private_message.rs b/crates/apub/src/objects/private_message.rs index f683a989f..be60cc4fa 100644 --- a/crates/apub/src/objects/private_message.rs +++ b/crates/apub/src/objects/private_message.rs @@ -22,7 +22,7 @@ use lemmy_db_schema::{ }; use lemmy_utils::{ error::{LemmyError, LemmyErrorType}, - utils::{markdown::markdown_to_html, time::convert_datetime}, + utils::markdown::markdown_to_html, }; use std::ops::Deref; use url::Url; @@ -86,8 +86,8 @@ impl Object for ApubPrivateMessage { content: markdown_to_html(&self.content), media_type: Some(MediaTypeHtml::Html), source: Some(Source::new(self.content.clone())), - published: Some(convert_datetime(self.published)), - updated: self.updated.map(convert_datetime), + published: Some(self.published), + updated: self.updated, }; Ok(note) } diff --git a/crates/db_schema/src/impls/moderator.rs b/crates/db_schema/src/impls/moderator.rs index a4c300b2a..012e05394 100644 --- a/crates/db_schema/src/impls/moderator.rs +++ b/crates/db_schema/src/impls/moderator.rs @@ -651,7 +651,6 @@ mod tests { community_id: inserted_community.id, reason: None, removed: None, - expires: None, }; let inserted_mod_remove_community = ModRemoveCommunity::create(pool, &mod_remove_community_form) @@ -667,7 +666,6 @@ mod tests { mod_person_id: inserted_mod.id, reason: None, removed: true, - expires: None, when_: inserted_mod_remove_community.when_, }; diff --git a/crates/db_schema/src/schema.rs b/crates/db_schema/src/schema.rs index 2d6f221fc..440cb09fa 100644 --- a/crates/db_schema/src/schema.rs +++ b/crates/db_schema/src/schema.rs @@ -563,7 +563,6 @@ diesel::table! { community_id -> Int4, reason -> Nullable, removed -> Bool, - expires -> Nullable, when_ -> Timestamptz, } } diff --git a/crates/db_schema/src/source/moderator.rs b/crates/db_schema/src/source/moderator.rs index 7e2ff2867..181bdbab7 100644 --- a/crates/db_schema/src/source/moderator.rs +++ b/crates/db_schema/src/source/moderator.rs @@ -127,7 +127,6 @@ pub struct ModRemoveCommunity { pub community_id: CommunityId, pub reason: Option, pub removed: bool, - pub expires: Option>, pub when_: DateTime, } @@ -138,7 +137,6 @@ pub struct ModRemoveCommunityForm { pub community_id: CommunityId, pub reason: Option, pub removed: Option, - pub expires: Option>, } #[skip_serializing_none] diff --git a/crates/utils/src/error.rs b/crates/utils/src/error.rs index 3b5da1bc9..714fdfe56 100644 --- a/crates/utils/src/error.rs +++ b/crates/utils/src/error.rs @@ -221,6 +221,8 @@ pub enum LemmyErrorType { /// Thrown when an API call is submitted with more than 1000 array elements, see [[MAX_API_PARAM_ELEMENTS]] TooManyItems, CommunityHasNoFollowers, + BanExpirationInPast, + InvalidUnixTime, Unknown(String), } diff --git a/crates/utils/src/utils/mod.rs b/crates/utils/src/utils/mod.rs index 04be57d34..e9ba7f84d 100644 --- a/crates/utils/src/utils/mod.rs +++ b/crates/utils/src/utils/mod.rs @@ -1,5 +1,4 @@ pub mod markdown; pub mod mention; pub mod slurs; -pub mod time; pub mod validation; diff --git a/crates/utils/src/utils/time.rs b/crates/utils/src/utils/time.rs deleted file mode 100644 index 1f7ebe145..000000000 --- a/crates/utils/src/utils/time.rs +++ /dev/null @@ -1,12 +0,0 @@ -use chrono::{DateTime, TimeZone, Utc}; - -pub fn naive_from_unix(time: i64) -> DateTime { - Utc - .timestamp_opt(time, 0) - .single() - .expect("convert datetime") -} - -pub fn convert_datetime(datetime: DateTime) -> DateTime { - datetime -} diff --git a/migrations/2023-10-17-181800_drop_remove_community_expires/down.sql b/migrations/2023-10-17-181800_drop_remove_community_expires/down.sql new file mode 100644 index 000000000..048c3d7f1 --- /dev/null +++ b/migrations/2023-10-17-181800_drop_remove_community_expires/down.sql @@ -0,0 +1,3 @@ +ALTER TABLE mod_remove_community + ADD COLUMN expires timestamp; + diff --git a/migrations/2023-10-17-181800_drop_remove_community_expires/up.sql b/migrations/2023-10-17-181800_drop_remove_community_expires/up.sql new file mode 100644 index 000000000..a94453096 --- /dev/null +++ b/migrations/2023-10-17-181800_drop_remove_community_expires/up.sql @@ -0,0 +1,3 @@ +ALTER TABLE mod_remove_community + DROP COLUMN expires; + From a675fecacd990ca29372f6b3be8421cb39981793 Mon Sep 17 00:00:00 2001 From: Dessalines Date: Tue, 17 Oct 2023 14:13:44 -0400 Subject: [PATCH 17/24] Version 0.19.0-rc.2 --- Cargo.lock | 24 ++++++++++++------------ Cargo.toml | 24 ++++++++++++------------ 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5cf23c6a5..debd8bcc5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2621,7 +2621,7 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "lemmy_api" -version = "0.19.0-beta.7" +version = "0.19.0-rc.2" dependencies = [ "activitypub_federation", "actix-web", @@ -2652,7 +2652,7 @@ dependencies = [ [[package]] name = "lemmy_api_common" -version = "0.19.0-beta.7" +version = "0.19.0-rc.2" dependencies = [ "activitypub_federation", "actix-web", @@ -2686,7 +2686,7 @@ dependencies = [ [[package]] name = "lemmy_api_crud" -version = "0.19.0-beta.7" +version = "0.19.0-rc.2" dependencies = [ "activitypub_federation", "actix-web", @@ -2707,7 +2707,7 @@ dependencies = [ [[package]] name = "lemmy_apub" -version = "0.19.0-beta.7" +version = "0.19.0-rc.2" dependencies = [ "activitypub_federation", "actix-web", @@ -2746,7 +2746,7 @@ dependencies = [ [[package]] name = "lemmy_db_schema" -version = "0.19.0-beta.7" +version = "0.19.0-rc.2" dependencies = [ "activitypub_federation", "async-trait", @@ -2782,7 +2782,7 @@ dependencies = [ [[package]] name = "lemmy_db_views" -version = "0.19.0-beta.7" +version = "0.19.0-rc.2" dependencies = [ "actix-web", "diesel", @@ -2800,7 +2800,7 @@ dependencies = [ [[package]] name = "lemmy_db_views_actor" -version = "0.19.0-beta.7" +version = "0.19.0-rc.2" dependencies = [ "chrono", "diesel", @@ -2815,7 +2815,7 @@ dependencies = [ [[package]] name = "lemmy_db_views_moderator" -version = "0.19.0-beta.7" +version = "0.19.0-rc.2" dependencies = [ "diesel", "diesel-async", @@ -2827,7 +2827,7 @@ dependencies = [ [[package]] name = "lemmy_federate" -version = "0.19.0-beta.7" +version = "0.19.0-rc.2" dependencies = [ "activitypub_federation", "anyhow", @@ -2859,7 +2859,7 @@ dependencies = [ [[package]] name = "lemmy_routes" -version = "0.19.0-beta.7" +version = "0.19.0-rc.2" dependencies = [ "activitypub_federation", "actix-web", @@ -2884,7 +2884,7 @@ dependencies = [ [[package]] name = "lemmy_server" -version = "0.19.0-beta.7" +version = "0.19.0-rc.2" dependencies = [ "activitypub_federation", "actix-cors", @@ -2932,7 +2932,7 @@ dependencies = [ [[package]] name = "lemmy_utils" -version = "0.19.0-beta.7" +version = "0.19.0-rc.2" dependencies = [ "actix-web", "anyhow", diff --git a/Cargo.toml b/Cargo.toml index 9c3fa787e..356abb035 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace.package] -version = "0.19.0-beta.7" +version = "0.19.0-rc.2" edition = "2021" description = "A link aggregator for the fediverse" license = "AGPL-3.0" @@ -60,16 +60,16 @@ members = [ ] [workspace.dependencies] -lemmy_api = { version = "=0.19.0-beta.7", path = "./crates/api" } -lemmy_api_crud = { version = "=0.19.0-beta.7", path = "./crates/api_crud" } -lemmy_apub = { version = "=0.19.0-beta.7", path = "./crates/apub" } -lemmy_utils = { version = "=0.19.0-beta.7", path = "./crates/utils" } -lemmy_db_schema = { version = "=0.19.0-beta.7", path = "./crates/db_schema" } -lemmy_api_common = { version = "=0.19.0-beta.7", path = "./crates/api_common" } -lemmy_routes = { version = "=0.19.0-beta.7", path = "./crates/routes" } -lemmy_db_views = { version = "=0.19.0-beta.7", path = "./crates/db_views" } -lemmy_db_views_actor = { version = "=0.19.0-beta.7", path = "./crates/db_views_actor" } -lemmy_db_views_moderator = { version = "=0.19.0-beta.7", path = "./crates/db_views_moderator" } +lemmy_api = { version = "=0.19.0-rc.2", path = "./crates/api" } +lemmy_api_crud = { version = "=0.19.0-rc.2", path = "./crates/api_crud" } +lemmy_apub = { version = "=0.19.0-rc.2", path = "./crates/apub" } +lemmy_utils = { version = "=0.19.0-rc.2", path = "./crates/utils" } +lemmy_db_schema = { version = "=0.19.0-rc.2", path = "./crates/db_schema" } +lemmy_api_common = { version = "=0.19.0-rc.2", path = "./crates/api_common" } +lemmy_routes = { version = "=0.19.0-rc.2", path = "./crates/routes" } +lemmy_db_views = { version = "=0.19.0-rc.2", path = "./crates/db_views" } +lemmy_db_views_actor = { version = "=0.19.0-rc.2", path = "./crates/db_views_actor" } +lemmy_db_views_moderator = { version = "=0.19.0-rc.2", path = "./crates/db_views_moderator" } activitypub_federation = { version = "0.5.0-beta.3", default-features = false, features = [ "actix-web", ] } @@ -138,7 +138,7 @@ lemmy_utils = { workspace = true } lemmy_db_schema = { workspace = true } lemmy_api_common = { workspace = true } lemmy_routes = { workspace = true } -lemmy_federate = { version = "0.19.0-beta.7", path = "crates/federate" } +lemmy_federate = { version = "0.19.0-rc.2", path = "crates/federate" } activitypub_federation = { workspace = true } diesel = { workspace = true } diesel-async = { workspace = true } From a14657d124da22cf90a4c1b1e24f19cc13624c07 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Thu, 19 Oct 2023 06:31:51 -0700 Subject: [PATCH 18/24] Refactor rate limiter and improve rate limit bucket cleanup (#3937) * Update rate_limiter.rs * Update mod.rs * Update rate_limiter.rs * Update rate_limiter.rs * Update mod.rs * Update scheduled_tasks.rs * Shrink `RateLimitBucket` * Update rate_limiter.rs * Update mod.rs * Update rate_limiter.rs * Update rate_limiter.rs * Update rate_limiter.rs * Update rate_limiter.rs * Update mod.rs * Update rate_limiter.rs * fmt * Update rate_limiter.rs * Update rate_limiter.rs * Update rate_limiter.rs * Update rate_limiter.rs * Update rate_limiter.rs * Update rate_limiter.rs * Update rate_limiter.rs * Update rate_limiter.rs * Update rate_limiter.rs * rerun ci * Update rate_limiter.rs * Undo changes to fields * Manually undo changes to RateLimitBucket fields * fmt * Bucket cleanup loop in rate_limit/mod.rs * Remove rate limit bucket cleanup from scheduled_tasks.rs * Remove ; * Remove UNINITIALIZED_TOKEN_AMOUNT * Update rate_limiter.rs * fmt * Update rate_limiter.rs * fmt * Update rate_limiter.rs * fmt * Update rate_limiter.rs * stuff * MapLevel trait * fix merge * Prevent negative numbers in buckets * Clean up MapLevel::check * MapLevel::remove_full_buckets * stuff * Use remove_full_buckets to avoid allocations * stuff * remove tx * Remove RateLimitConfig * Rename settings_updated_channel to rate_limit_cell * Remove global rate limit cell * impl Default for RateLimitCell * bucket_configs doc comment to explain EnumMap * improve test_rate_limiter * rename default to with_test_config --------- Co-authored-by: Dessalines Co-authored-by: Nutomic --- Cargo.lock | 1 + Cargo.toml | 1 + crates/api_common/Cargo.toml | 1 + crates/api_common/src/claims.rs | 6 +- crates/api_common/src/context.rs | 2 +- crates/api_common/src/utils.rs | 35 +- crates/api_crud/src/site/create.rs | 5 +- crates/api_crud/src/site/update.rs | 5 +- crates/apub/src/objects/mod.rs | 8 +- crates/utils/Cargo.toml | 2 +- crates/utils/src/rate_limit/mod.rs | 276 +++++-------- crates/utils/src/rate_limit/rate_limiter.rs | 420 ++++++++++++-------- src/lib.rs | 4 +- src/scheduled_tasks.rs | 11 - src/session_middleware.rs | 6 +- 15 files changed, 388 insertions(+), 395 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index debd8bcc5..073e0a95c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2659,6 +2659,7 @@ dependencies = [ "anyhow", "chrono", "encoding", + "enum-map", "futures", "getrandom", "jsonwebtoken", diff --git a/Cargo.toml b/Cargo.toml index 356abb035..9bf1000b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -129,6 +129,7 @@ rustls = { version = "0.21.3", features = ["dangerous_configuration"] } futures-util = "0.3.28" tokio-postgres = "0.7.8" tokio-postgres-rustls = "0.10.0" +enum-map = "2.6" [dependencies] lemmy_api = { workspace = true } diff --git a/crates/api_common/Cargo.toml b/crates/api_common/Cargo.toml index 5325350c8..a01e6008c 100644 --- a/crates/api_common/Cargo.toml +++ b/crates/api_common/Cargo.toml @@ -68,6 +68,7 @@ actix-web = { workspace = true, optional = true } jsonwebtoken = { version = "8.3.0", optional = true } # necessary for wasmt compilation getrandom = { version = "0.2.10", features = ["js"] } +enum-map = { workspace = true } [dev-dependencies] serial_test = { workspace = true } diff --git a/crates/api_common/src/claims.rs b/crates/api_common/src/claims.rs index 6676840dc..09191ad71 100644 --- a/crates/api_common/src/claims.rs +++ b/crates/api_common/src/claims.rs @@ -88,7 +88,7 @@ mod tests { traits::Crud, utils::build_db_pool_for_tests, }; - use lemmy_utils::rate_limit::{RateLimitCell, RateLimitConfig}; + use lemmy_utils::rate_limit::RateLimitCell; use reqwest::Client; use reqwest_middleware::ClientBuilder; use serial_test::serial; @@ -103,9 +103,7 @@ mod tests { pool_.clone(), ClientBuilder::new(Client::default()).build(), secret, - RateLimitCell::new(RateLimitConfig::builder().build()) - .await - .clone(), + RateLimitCell::with_test_config(), ); let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()) diff --git a/crates/api_common/src/context.rs b/crates/api_common/src/context.rs index 0d448ef97..888a98741 100644 --- a/crates/api_common/src/context.rs +++ b/crates/api_common/src/context.rs @@ -46,7 +46,7 @@ impl LemmyContext { pub fn secret(&self) -> &Secret { &self.secret } - pub fn settings_updated_channel(&self) -> &RateLimitCell { + pub fn rate_limit_cell(&self) -> &RateLimitCell { &self.rate_limit_cell } } diff --git a/crates/api_common/src/utils.rs b/crates/api_common/src/utils.rs index b3dcd7558..5ba9a34c3 100644 --- a/crates/api_common/src/utils.rs +++ b/crates/api_common/src/utils.rs @@ -7,6 +7,7 @@ use crate::{ use actix_web::cookie::{Cookie, SameSite}; use anyhow::Context; use chrono::{DateTime, Days, Local, TimeZone, Utc}; +use enum_map::{enum_map, EnumMap}; use lemmy_db_schema::{ newtypes::{CommunityId, DbUrl, PersonId, PostId}, source::{ @@ -34,7 +35,7 @@ use lemmy_utils::{ email::{send_email, translations::Lang}, error::{LemmyError, LemmyErrorExt, LemmyErrorType, LemmyResult}, location_info, - rate_limit::RateLimitConfig, + rate_limit::{ActionType, BucketConfig}, settings::structs::Settings, utils::slurs::build_slur_regex, }; @@ -390,25 +391,21 @@ fn lang_str_to_lang(lang: &str) -> Lang { } pub fn local_site_rate_limit_to_rate_limit_config( - local_site_rate_limit: &LocalSiteRateLimit, -) -> RateLimitConfig { - let l = local_site_rate_limit; - RateLimitConfig { - message: l.message, - message_per_second: l.message_per_second, - post: l.post, - post_per_second: l.post_per_second, - register: l.register, - register_per_second: l.register_per_second, - image: l.image, - image_per_second: l.image_per_second, - comment: l.comment, - comment_per_second: l.comment_per_second, - search: l.search, - search_per_second: l.search_per_second, - import_user_settings: l.import_user_settings, - import_user_settings_per_second: l.import_user_settings_per_second, + l: &LocalSiteRateLimit, +) -> EnumMap { + enum_map! { + ActionType::Message => (l.message, l.message_per_second), + ActionType::Post => (l.post, l.post_per_second), + ActionType::Register => (l.register, l.register_per_second), + ActionType::Image => (l.image, l.image_per_second), + ActionType::Comment => (l.comment, l.comment_per_second), + ActionType::Search => (l.search, l.search_per_second), + ActionType::ImportUserSettings => (l.import_user_settings, l.import_user_settings_per_second), } + .map(|_key, (capacity, secs_to_refill)| BucketConfig { + capacity: u32::try_from(capacity).unwrap_or(0), + secs_to_refill: u32::try_from(secs_to_refill).unwrap_or(0), + }) } pub fn local_site_to_slur_regex(local_site: &LocalSite) -> Option { diff --git a/crates/api_crud/src/site/create.rs b/crates/api_crud/src/site/create.rs index 61dfd7c77..1449f4844 100644 --- a/crates/api_crud/src/site/create.rs +++ b/crates/api_crud/src/site/create.rs @@ -119,10 +119,7 @@ pub async fn create_site( let rate_limit_config = local_site_rate_limit_to_rate_limit_config(&site_view.local_site_rate_limit); - context - .settings_updated_channel() - .send(rate_limit_config) - .await?; + context.rate_limit_cell().set_config(rate_limit_config); Ok(Json(SiteResponse { site_view, diff --git a/crates/api_crud/src/site/update.rs b/crates/api_crud/src/site/update.rs index 3afc79559..b9d8f6a7f 100644 --- a/crates/api_crud/src/site/update.rs +++ b/crates/api_crud/src/site/update.rs @@ -157,10 +157,7 @@ pub async fn update_site( let rate_limit_config = local_site_rate_limit_to_rate_limit_config(&site_view.local_site_rate_limit); - context - .settings_updated_channel() - .send(rate_limit_config) - .await?; + context.rate_limit_cell().set_config(rate_limit_config); Ok(Json(SiteResponse { site_view, diff --git a/crates/apub/src/objects/mod.rs b/crates/apub/src/objects/mod.rs index b3653172a..6e27c0d09 100644 --- a/crates/apub/src/objects/mod.rs +++ b/crates/apub/src/objects/mod.rs @@ -61,10 +61,7 @@ pub(crate) mod tests { use anyhow::anyhow; use lemmy_api_common::{context::LemmyContext, request::build_user_agent}; use lemmy_db_schema::{source::secret::Secret, utils::build_db_pool_for_tests}; - use lemmy_utils::{ - rate_limit::{RateLimitCell, RateLimitConfig}, - settings::SETTINGS, - }; + use lemmy_utils::{rate_limit::RateLimitCell, settings::SETTINGS}; use reqwest::{Client, Request, Response}; use reqwest_middleware::{ClientBuilder, Middleware, Next}; use task_local_extensions::Extensions; @@ -101,8 +98,7 @@ pub(crate) mod tests { jwt_secret: String::new(), }; - let rate_limit_config = RateLimitConfig::builder().build(); - let rate_limit_cell = RateLimitCell::new(rate_limit_config).await; + let rate_limit_cell = RateLimitCell::with_test_config(); let context = LemmyContext::create(pool, client, secret, rate_limit_cell.clone()); let config = FederationConfig::builder() diff --git a/crates/utils/Cargo.toml b/crates/utils/Cargo.toml index 20611702e..dc9714b0d 100644 --- a/crates/utils/Cargo.toml +++ b/crates/utils/Cargo.toml @@ -47,7 +47,7 @@ smart-default = "0.7.1" lettre = { version = "0.10.4", features = ["tokio1", "tokio1-native-tls"] } markdown-it = "0.5.1" ts-rs = { workspace = true, optional = true } -enum-map = "2.6" +enum-map = { workspace = true } [dev-dependencies] reqwest = { workspace = true } diff --git a/crates/utils/src/rate_limit/mod.rs b/crates/utils/src/rate_limit/mod.rs index 114daf452..63090749b 100644 --- a/crates/utils/src/rate_limit/mod.rs +++ b/crates/utils/src/rate_limit/mod.rs @@ -1,9 +1,9 @@ use crate::error::{LemmyError, LemmyErrorType}; use actix_web::dev::{ConnectionInfo, Service, ServiceRequest, ServiceResponse, Transform}; -use enum_map::enum_map; +use enum_map::{enum_map, EnumMap}; use futures::future::{ok, Ready}; -use rate_limiter::{InstantSecs, RateLimitStorage, RateLimitType}; -use serde::{Deserialize, Serialize}; +pub use rate_limiter::{ActionType, BucketConfig}; +use rate_limiter::{InstantSecs, RateLimitState}; use std::{ future::Future, net::{IpAddr, Ipv4Addr, SocketAddr}, @@ -14,208 +14,140 @@ use std::{ task::{Context, Poll}, time::Duration, }; -use tokio::sync::{mpsc, mpsc::Sender, OnceCell}; -use typed_builder::TypedBuilder; pub mod rate_limiter; -#[derive(Debug, Deserialize, Serialize, Clone, TypedBuilder)] -pub struct RateLimitConfig { - #[builder(default = 180)] - /// Maximum number of messages created in interval - pub message: i32, - #[builder(default = 60)] - /// Interval length for message limit, in seconds - pub message_per_second: i32, - #[builder(default = 6)] - /// Maximum number of posts created in interval - pub post: i32, - #[builder(default = 300)] - /// Interval length for post limit, in seconds - pub post_per_second: i32, - #[builder(default = 3)] - /// Maximum number of registrations in interval - pub register: i32, - #[builder(default = 3600)] - /// Interval length for registration limit, in seconds - pub register_per_second: i32, - #[builder(default = 6)] - /// Maximum number of image uploads in interval - pub image: i32, - #[builder(default = 3600)] - /// Interval length for image uploads, in seconds - pub image_per_second: i32, - #[builder(default = 6)] - /// Maximum number of comments created in interval - pub comment: i32, - #[builder(default = 600)] - /// Interval length for comment limit, in seconds - pub comment_per_second: i32, - #[builder(default = 60)] - /// Maximum number of searches created in interval - pub search: i32, - #[builder(default = 600)] - /// Interval length for search limit, in seconds - pub search_per_second: i32, - #[builder(default = 1)] - /// Maximum number of user settings imports in interval - pub import_user_settings: i32, - #[builder(default = 24 * 60 * 60)] - /// Interval length for importing user settings, in seconds (defaults to 24 hours) - pub import_user_settings_per_second: i32, -} - #[derive(Debug, Clone)] -struct RateLimit { - pub rate_limiter: RateLimitStorage, - pub rate_limit_config: RateLimitConfig, -} - -#[derive(Debug, Clone)] -pub struct RateLimitedGuard { - rate_limit: Arc>, - type_: RateLimitType, +pub struct RateLimitChecker { + state: Arc>, + action_type: ActionType, } /// Single instance of rate limit config and buckets, which is shared across all threads. #[derive(Clone)] pub struct RateLimitCell { - tx: Sender, - rate_limit: Arc>, + state: Arc>, } impl RateLimitCell { - /// Initialize cell if it wasnt initialized yet. Otherwise returns the existing cell. - pub async fn new(rate_limit_config: RateLimitConfig) -> &'static Self { - static LOCAL_INSTANCE: OnceCell = OnceCell::const_new(); - LOCAL_INSTANCE - .get_or_init(|| async { - let (tx, mut rx) = mpsc::channel::(4); - let rate_limit = Arc::new(Mutex::new(RateLimit { - rate_limiter: Default::default(), - rate_limit_config, - })); - let rate_limit2 = rate_limit.clone(); - tokio::spawn(async move { - while let Some(r) = rx.recv().await { - rate_limit2 - .lock() - .expect("Failed to lock rate limit mutex for updating") - .rate_limit_config = r; - } - }); - RateLimitCell { tx, rate_limit } - }) - .await + pub fn new(rate_limit_config: EnumMap) -> Self { + let state = Arc::new(Mutex::new(RateLimitState::new(rate_limit_config))); + + let state_weak_ref = Arc::downgrade(&state); + + tokio::spawn(async move { + let hour = Duration::from_secs(3600); + + // This loop stops when all other references to `state` are dropped + while let Some(state) = state_weak_ref.upgrade() { + tokio::time::sleep(hour).await; + state + .lock() + .expect("Failed to lock rate limit mutex for reading") + .remove_full_buckets(InstantSecs::now()); + } + }); + + RateLimitCell { state } } - /// Call this when the config was updated, to update all in-memory cells. - pub async fn send(&self, config: RateLimitConfig) -> Result<(), LemmyError> { - self.tx.send(config).await?; - Ok(()) - } - - /// Remove buckets older than the given duration - pub fn remove_older_than(&self, mut duration: Duration) { - let mut guard = self - .rate_limit + pub fn set_config(&self, config: EnumMap) { + self + .state .lock() - .expect("Failed to lock rate limit mutex for reading"); - let rate_limit = &guard.rate_limit_config; + .expect("Failed to lock rate limit mutex for updating") + .set_config(config); + } - // If any rate limit interval is greater than `duration`, then the largest interval is used instead. This preserves buckets that would not pass the rate limit check. - let max_interval_secs = enum_map! { - RateLimitType::Message => rate_limit.message_per_second, - RateLimitType::Post => rate_limit.post_per_second, - RateLimitType::Register => rate_limit.register_per_second, - RateLimitType::Image => rate_limit.image_per_second, - RateLimitType::Comment => rate_limit.comment_per_second, - RateLimitType::Search => rate_limit.search_per_second, - RateLimitType::ImportUserSettings => rate_limit.import_user_settings_per_second + pub fn message(&self) -> RateLimitChecker { + self.new_checker(ActionType::Message) + } + + pub fn post(&self) -> RateLimitChecker { + self.new_checker(ActionType::Post) + } + + pub fn register(&self) -> RateLimitChecker { + self.new_checker(ActionType::Register) + } + + pub fn image(&self) -> RateLimitChecker { + self.new_checker(ActionType::Image) + } + + pub fn comment(&self) -> RateLimitChecker { + self.new_checker(ActionType::Comment) + } + + pub fn search(&self) -> RateLimitChecker { + self.new_checker(ActionType::Search) + } + + pub fn import_user_settings(&self) -> RateLimitChecker { + self.new_checker(ActionType::ImportUserSettings) + } + + fn new_checker(&self, action_type: ActionType) -> RateLimitChecker { + RateLimitChecker { + state: self.state.clone(), + action_type, } - .into_values() - .max() - .and_then(|max| u64::try_from(max).ok()) - .unwrap_or(0); - - duration = std::cmp::max(duration, Duration::from_secs(max_interval_secs)); - - guard - .rate_limiter - .remove_older_than(duration, InstantSecs::now()) } - pub fn message(&self) -> RateLimitedGuard { - self.kind(RateLimitType::Message) - } - - pub fn post(&self) -> RateLimitedGuard { - self.kind(RateLimitType::Post) - } - - pub fn register(&self) -> RateLimitedGuard { - self.kind(RateLimitType::Register) - } - - pub fn image(&self) -> RateLimitedGuard { - self.kind(RateLimitType::Image) - } - - pub fn comment(&self) -> RateLimitedGuard { - self.kind(RateLimitType::Comment) - } - - pub fn search(&self) -> RateLimitedGuard { - self.kind(RateLimitType::Search) - } - - pub fn import_user_settings(&self) -> RateLimitedGuard { - self.kind(RateLimitType::ImportUserSettings) - } - - fn kind(&self, type_: RateLimitType) -> RateLimitedGuard { - RateLimitedGuard { - rate_limit: self.rate_limit.clone(), - type_, - } + pub fn with_test_config() -> Self { + Self::new(enum_map! { + ActionType::Message => BucketConfig { + capacity: 180, + secs_to_refill: 60, + }, + ActionType::Post => BucketConfig { + capacity: 6, + secs_to_refill: 300, + }, + ActionType::Register => BucketConfig { + capacity: 3, + secs_to_refill: 3600, + }, + ActionType::Image => BucketConfig { + capacity: 6, + secs_to_refill: 3600, + }, + ActionType::Comment => BucketConfig { + capacity: 6, + secs_to_refill: 600, + }, + ActionType::Search => BucketConfig { + capacity: 60, + secs_to_refill: 600, + }, + ActionType::ImportUserSettings => BucketConfig { + capacity: 1, + secs_to_refill: 24 * 60 * 60, + }, + }) } } pub struct RateLimitedMiddleware { - rate_limited: RateLimitedGuard, + checker: RateLimitChecker, service: Rc, } -impl RateLimitedGuard { +impl RateLimitChecker { /// Returns true if the request passed the rate limit, false if it failed and should be rejected. pub fn check(self, ip_addr: IpAddr) -> bool { // Does not need to be blocking because the RwLock in settings never held across await points, // and the operation here locks only long enough to clone - let mut guard = self - .rate_limit + let mut state = self + .state .lock() .expect("Failed to lock rate limit mutex for reading"); - let rate_limit = &guard.rate_limit_config; - let (kind, interval) = match self.type_ { - RateLimitType::Message => (rate_limit.message, rate_limit.message_per_second), - RateLimitType::Post => (rate_limit.post, rate_limit.post_per_second), - RateLimitType::Register => (rate_limit.register, rate_limit.register_per_second), - RateLimitType::Image => (rate_limit.image, rate_limit.image_per_second), - RateLimitType::Comment => (rate_limit.comment, rate_limit.comment_per_second), - RateLimitType::Search => (rate_limit.search, rate_limit.search_per_second), - RateLimitType::ImportUserSettings => ( - rate_limit.import_user_settings, - rate_limit.import_user_settings_per_second, - ), - }; - let limiter = &mut guard.rate_limiter; - - limiter.check_rate_limit_full(self.type_, ip_addr, kind, interval, InstantSecs::now()) + state.check(self.action_type, ip_addr, InstantSecs::now()) } } -impl Transform for RateLimitedGuard +impl Transform for RateLimitChecker where S: Service + 'static, S::Future: 'static, @@ -228,7 +160,7 @@ where fn new_transform(&self, service: S) -> Self::Future { ok(RateLimitedMiddleware { - rate_limited: self.clone(), + checker: self.clone(), service: Rc::new(service), }) } @@ -252,11 +184,11 @@ where fn call(&self, req: ServiceRequest) -> Self::Future { let ip_addr = get_ip(&req.connection_info()); - let rate_limited = self.rate_limited.clone(); + let checker = self.checker.clone(); let service = self.service.clone(); Box::pin(async move { - if rate_limited.check(ip_addr) { + if checker.check(ip_addr) { service.call(req).await } else { let (http_req, _) = req.into_parts(); diff --git a/crates/utils/src/rate_limit/rate_limiter.rs b/crates/utils/src/rate_limit/rate_limiter.rs index 7ba1345c5..d0dad5df2 100644 --- a/crates/utils/src/rate_limit/rate_limiter.rs +++ b/crates/utils/src/rate_limit/rate_limiter.rs @@ -1,15 +1,13 @@ -use enum_map::{enum_map, EnumMap}; +use enum_map::EnumMap; use once_cell::sync::Lazy; use std::{ collections::HashMap, hash::Hash, net::{IpAddr, Ipv4Addr, Ipv6Addr}, - time::{Duration, Instant}, + time::Instant, }; use tracing::debug; -const UNINITIALIZED_TOKEN_AMOUNT: f32 = -2.0; - static START_TIME: Lazy = Lazy::new(Instant::now); /// Smaller than `std::time::Instant` because it uses a smaller integer for seconds and doesn't @@ -26,27 +24,48 @@ impl InstantSecs { .expect("server has been running for over 136 years"), } } - - fn secs_since(self, earlier: Self) -> u32 { - self.secs.saturating_sub(earlier.secs) - } - - fn to_instant(self) -> Instant { - *START_TIME + Duration::from_secs(self.secs.into()) - } } -#[derive(PartialEq, Debug, Clone)] -struct RateLimitBucket { +#[derive(PartialEq, Debug, Clone, Copy)] +struct Bucket { last_checked: InstantSecs, /// This field stores the amount of tokens that were present at `last_checked`. /// The amount of tokens steadily increases until it reaches the bucket's capacity. /// Performing the rate-limited action consumes 1 token. - tokens: f32, + tokens: u32, +} + +#[derive(PartialEq, Debug, Copy, Clone)] +pub struct BucketConfig { + pub capacity: u32, + pub secs_to_refill: u32, +} + +impl Bucket { + fn update(self, now: InstantSecs, config: BucketConfig) -> Self { + let secs_since_last_checked = now.secs.saturating_sub(self.last_checked.secs); + + // For `secs_since_last_checked` seconds, the amount of tokens increases by `capacity` every `secs_to_refill` seconds. + // The amount of tokens added per second is `capacity / secs_to_refill`. + // The expression below is like `secs_since_last_checked * (capacity / secs_to_refill)` but with precision and non-overflowing multiplication. + let added_tokens = u64::from(secs_since_last_checked) * u64::from(config.capacity) + / u64::from(config.secs_to_refill); + + // The amount of tokens there would be if the bucket had infinite capacity + let unbounded_tokens = self.tokens + (added_tokens as u32); + + // Bucket stops filling when capacity is reached + let tokens = std::cmp::min(unbounded_tokens, config.capacity); + + Bucket { + last_checked: now, + tokens, + } + } } #[derive(Debug, enum_map::Enum, Copy, Clone, AsRefStr)] -pub(crate) enum RateLimitType { +pub enum ActionType { Message, Register, Post, @@ -56,179 +75,228 @@ pub(crate) enum RateLimitType { ImportUserSettings, } -type Map = HashMap>; - #[derive(PartialEq, Debug, Clone)] struct RateLimitedGroup { - total: EnumMap, + total: EnumMap, children: C, } +type Map = HashMap>; + +/// Implemented for `()`, `Map`, `Map>`, etc. +trait MapLevel: Default { + type CapacityFactors; + type AddrParts; + + fn check( + &mut self, + action_type: ActionType, + now: InstantSecs, + configs: EnumMap, + capacity_factors: Self::CapacityFactors, + addr_parts: Self::AddrParts, + ) -> bool; + + /// Remove full buckets and return `true` if there's any buckets remaining + fn remove_full_buckets( + &mut self, + now: InstantSecs, + configs: EnumMap, + ) -> bool; +} + +impl MapLevel for Map { + type CapacityFactors = (u32, C::CapacityFactors); + type AddrParts = (K, C::AddrParts); + + fn check( + &mut self, + action_type: ActionType, + now: InstantSecs, + configs: EnumMap, + (capacity_factor, child_capacity_factors): Self::CapacityFactors, + (addr_part, child_addr_parts): Self::AddrParts, + ) -> bool { + // Multiplies capacities by `capacity_factor` for groups in `self` + let adjusted_configs = configs.map(|_, config| BucketConfig { + capacity: config.capacity.saturating_mul(capacity_factor), + ..config + }); + + // Remove groups that are no longer needed if the hash map's existing allocation has no space for new groups. + // This is done before calling `HashMap::entry` because that immediately allocates just like `HashMap::insert`. + if (self.capacity() == self.len()) && !self.contains_key(&addr_part) { + self.remove_full_buckets(now, configs); + } + + let group = self + .entry(addr_part) + .or_insert(RateLimitedGroup::new(now, adjusted_configs)); + + #[allow(clippy::indexing_slicing)] + let total_passes = group.check_total(action_type, now, adjusted_configs[action_type]); + + let children_pass = group.children.check( + action_type, + now, + configs, + child_capacity_factors, + child_addr_parts, + ); + + total_passes && children_pass + } + + fn remove_full_buckets( + &mut self, + now: InstantSecs, + configs: EnumMap, + ) -> bool { + self.retain(|_key, group| { + let some_children_remaining = group.children.remove_full_buckets(now, configs); + + // Evaluated if `some_children_remaining` is false + let total_has_refill_in_future = || { + group.total.into_iter().all(|(action_type, bucket)| { + #[allow(clippy::indexing_slicing)] + let config = configs[action_type]; + bucket.update(now, config).tokens != config.capacity + }) + }; + + some_children_remaining || total_has_refill_in_future() + }); + + self.shrink_to_fit(); + + !self.is_empty() + } +} + +impl MapLevel for () { + type CapacityFactors = (); + type AddrParts = (); + + fn check( + &mut self, + _: ActionType, + _: InstantSecs, + _: EnumMap, + _: Self::CapacityFactors, + _: Self::AddrParts, + ) -> bool { + true + } + + fn remove_full_buckets(&mut self, _: InstantSecs, _: EnumMap) -> bool { + false + } +} + impl RateLimitedGroup { - fn new(now: InstantSecs) -> Self { + fn new(now: InstantSecs, configs: EnumMap) -> Self { RateLimitedGroup { - total: enum_map! { - _ => RateLimitBucket { - last_checked: now, - tokens: UNINITIALIZED_TOKEN_AMOUNT, - }, - }, + total: configs.map(|_, config| Bucket { + last_checked: now, + tokens: config.capacity, + }), + // `HashMap::new()` or `()` children: Default::default(), } } fn check_total( &mut self, - type_: RateLimitType, + action_type: ActionType, now: InstantSecs, - capacity: i32, - secs_to_refill: i32, + config: BucketConfig, ) -> bool { - let capacity = capacity as f32; - let secs_to_refill = secs_to_refill as f32; - #[allow(clippy::indexing_slicing)] // `EnumMap` has no `get` funciton - let bucket = &mut self.total[type_]; + let bucket = &mut self.total[action_type]; - if bucket.tokens == UNINITIALIZED_TOKEN_AMOUNT { - bucket.tokens = capacity; - } + let new_bucket = bucket.update(now, config); - let secs_since_last_checked = now.secs_since(bucket.last_checked) as f32; - bucket.last_checked = now; - - // For `secs_since_last_checked` seconds, increase `bucket.tokens` - // by `capacity` every `secs_to_refill` seconds - bucket.tokens += { - let tokens_per_sec = capacity / secs_to_refill; - secs_since_last_checked * tokens_per_sec - }; - - // Prevent `bucket.tokens` from exceeding `capacity` - if bucket.tokens > capacity { - bucket.tokens = capacity; - } - - if bucket.tokens < 1.0 { + if new_bucket.tokens == 0 { // Not enough tokens yet - debug!( - "Rate limited type: {}, time_passed: {}, allowance: {}", - type_.as_ref(), - secs_since_last_checked, - bucket.tokens - ); + // Setting `bucket` to `new_bucket` here is useless and would cause the bucket to start over at 0 tokens because of rounding false } else { // Consume 1 token - bucket.tokens -= 1.0; + *bucket = new_bucket; + bucket.tokens -= 1; true } } } /// Rate limiting based on rate type and IP addr -#[derive(PartialEq, Debug, Clone, Default)] -pub struct RateLimitStorage { - /// One bucket per individual IPv4 address +#[derive(PartialEq, Debug, Clone)] +pub struct RateLimitState { + /// Each individual IPv4 address gets one `RateLimitedGroup`. ipv4_buckets: Map, - /// Seperate buckets for 48, 56, and 64 bit prefixes of IPv6 addresses + /// All IPv6 addresses that share the same first 64 bits share the same `RateLimitedGroup`. + /// + /// The same thing happens for the first 48 and 56 bits, but with increased capacity. + /// + /// This is done because all users can easily switch to any other IPv6 address that has the same first 64 bits. + /// It could be as low as 48 bits for some networks, which is the reason for 48 and 56 bit address groups. ipv6_buckets: Map<[u8; 6], Map>>, + /// This stores a `BucketConfig` for each `ActionType`. `EnumMap` makes it impossible to have a missing `BucketConfig`. + bucket_configs: EnumMap, } -impl RateLimitStorage { +impl RateLimitState { + pub fn new(bucket_configs: EnumMap) -> Self { + RateLimitState { + ipv4_buckets: HashMap::new(), + ipv6_buckets: HashMap::new(), + bucket_configs, + } + } + /// Rate limiting Algorithm described here: https://stackoverflow.com/a/668327/1655478 /// /// Returns true if the request passed the rate limit, false if it failed and should be rejected. - pub(super) fn check_rate_limit_full( - &mut self, - type_: RateLimitType, - ip: IpAddr, - capacity: i32, - secs_to_refill: i32, - now: InstantSecs, - ) -> bool { - let mut result = true; - - match ip { + pub fn check(&mut self, action_type: ActionType, ip: IpAddr, now: InstantSecs) -> bool { + let result = match ip { IpAddr::V4(ipv4) => { - // Only used by one address. - let group = self + self .ipv4_buckets - .entry(ipv4) - .or_insert(RateLimitedGroup::new(now)); - - result &= group.check_total(type_, now, capacity, secs_to_refill); + .check(action_type, now, self.bucket_configs, (1, ()), (ipv4, ())) } IpAddr::V6(ipv6) => { let (key_48, key_56, key_64) = split_ipv6(ipv6); - - // Contains all addresses with the same first 48 bits. These addresses might be part of the same network. - let group_48 = self - .ipv6_buckets - .entry(key_48) - .or_insert(RateLimitedGroup::new(now)); - result &= group_48.check_total(type_, now, capacity.saturating_mul(16), secs_to_refill); - - // Contains all addresses with the same first 56 bits. These addresses might be part of the same network. - let group_56 = group_48 - .children - .entry(key_56) - .or_insert(RateLimitedGroup::new(now)); - result &= group_56.check_total(type_, now, capacity.saturating_mul(4), secs_to_refill); - - // A group with no children. It is shared by all addresses with the same first 64 bits. These addresses are always part of the same network. - let group_64 = group_56 - .children - .entry(key_64) - .or_insert(RateLimitedGroup::new(now)); - - result &= group_64.check_total(type_, now, capacity, secs_to_refill); + self.ipv6_buckets.check( + action_type, + now, + self.bucket_configs, + (16, (4, (1, ()))), + (key_48, (key_56, (key_64, ()))), + ) } }; if !result { - debug!("Rate limited IP: {ip}"); + debug!("Rate limited IP: {ip}, type: {action_type:?}"); } result } - /// Remove buckets older than the given duration - pub(super) fn remove_older_than(&mut self, duration: Duration, now: InstantSecs) { - // Only retain buckets that were last used after `instant` - let Some(instant) = now.to_instant().checked_sub(duration) else { - return; - }; - - let is_recently_used = |group: &RateLimitedGroup<_>| { - group - .total - .values() - .all(|bucket| bucket.last_checked.to_instant() > instant) - }; - - retain_and_shrink(&mut self.ipv4_buckets, |_, group| is_recently_used(group)); - - retain_and_shrink(&mut self.ipv6_buckets, |_, group_48| { - retain_and_shrink(&mut group_48.children, |_, group_56| { - retain_and_shrink(&mut group_56.children, |_, group_64| { - is_recently_used(group_64) - }); - !group_56.children.is_empty() - }); - !group_48.children.is_empty() - }) + /// Remove buckets that are now full + pub fn remove_full_buckets(&mut self, now: InstantSecs) { + self + .ipv4_buckets + .remove_full_buckets(now, self.bucket_configs); + self + .ipv6_buckets + .remove_full_buckets(now, self.bucket_configs); } -} -fn retain_and_shrink(map: &mut HashMap, f: F) -where - K: Eq + Hash, - F: FnMut(&K, &mut V) -> bool, -{ - map.retain(f); - map.shrink_to_fit(); + pub fn set_config(&mut self, new_configs: EnumMap) { + self.bucket_configs = new_configs; + } } fn split_ipv6(ip: Ipv6Addr) -> ([u8; 6], u8, u8) { @@ -241,6 +309,8 @@ mod tests { #![allow(clippy::unwrap_used)] #![allow(clippy::indexing_slicing)] + use super::{ActionType, BucketConfig, InstantSecs, RateLimitState, RateLimitedGroup}; + #[test] fn test_split_ipv6() { let ip = std::net::Ipv6Addr::new( @@ -254,9 +324,20 @@ mod tests { #[test] fn test_rate_limiter() { - let mut rate_limiter = super::RateLimitStorage::default(); - let mut now = super::InstantSecs::now(); + let bucket_configs = enum_map::enum_map! { + ActionType::Message => BucketConfig { + capacity: 2, + secs_to_refill: 1, + }, + _ => BucketConfig { + capacity: 2, + secs_to_refill: 1, + }, + }; + let mut rate_limiter = RateLimitState::new(bucket_configs); + let mut now = InstantSecs::now(); + // Do 1 `Message` and 1 `Post` action for each IP address, and expect the limit to not be reached let ips = [ "123.123.123.123", "1:2:3::", @@ -266,66 +347,71 @@ mod tests { ]; for ip in ips { let ip = ip.parse().unwrap(); - let message_passed = - rate_limiter.check_rate_limit_full(super::RateLimitType::Message, ip, 2, 1, now); - let post_passed = - rate_limiter.check_rate_limit_full(super::RateLimitType::Post, ip, 3, 1, now); + let message_passed = rate_limiter.check(ActionType::Message, ip, now); + let post_passed = rate_limiter.check(ActionType::Post, ip, now); assert!(message_passed); assert!(post_passed); } #[allow(clippy::indexing_slicing)] - let expected_buckets = |factor: f32, tokens_consumed: f32| { - let mut buckets = super::RateLimitedGroup::<()>::new(now).total; - buckets[super::RateLimitType::Message] = super::RateLimitBucket { - last_checked: now, - tokens: (2.0 * factor) - tokens_consumed, - }; - buckets[super::RateLimitType::Post] = super::RateLimitBucket { - last_checked: now, - tokens: (3.0 * factor) - tokens_consumed, - }; + let expected_buckets = |factor: u32, tokens_consumed: u32| { + let adjusted_configs = bucket_configs.map(|_, config| BucketConfig { + capacity: config.capacity.saturating_mul(factor), + ..config + }); + let mut buckets = RateLimitedGroup::<()>::new(now, adjusted_configs).total; + buckets[ActionType::Message].tokens -= tokens_consumed; + buckets[ActionType::Post].tokens -= tokens_consumed; buckets }; - let bottom_group = |tokens_consumed| super::RateLimitedGroup { - total: expected_buckets(1.0, tokens_consumed), + let bottom_group = |tokens_consumed| RateLimitedGroup { + total: expected_buckets(1, tokens_consumed), children: (), }; assert_eq!( rate_limiter, - super::RateLimitStorage { - ipv4_buckets: [([123, 123, 123, 123].into(), bottom_group(1.0)),].into(), + RateLimitState { + bucket_configs, + ipv4_buckets: [([123, 123, 123, 123].into(), bottom_group(1))].into(), ipv6_buckets: [( [0, 1, 0, 2, 0, 3], - super::RateLimitedGroup { - total: expected_buckets(16.0, 4.0), + RateLimitedGroup { + total: expected_buckets(16, 4), children: [ ( 0, - super::RateLimitedGroup { - total: expected_buckets(4.0, 1.0), - children: [(0, bottom_group(1.0)),].into(), + RateLimitedGroup { + total: expected_buckets(4, 1), + children: [(0, bottom_group(1))].into(), } ), ( 4, - super::RateLimitedGroup { - total: expected_buckets(4.0, 3.0), - children: [(0, bottom_group(1.0)), (5, bottom_group(2.0)),].into(), + RateLimitedGroup { + total: expected_buckets(4, 3), + children: [(0, bottom_group(1)), (5, bottom_group(2))].into(), } ), ] .into(), } - ),] + )] .into(), } ); + // Do 2 `Message` actions for 1 IP address and expect only the 2nd one to fail + for expected_to_pass in [true, false] { + let ip = "1:2:3:0400::".parse().unwrap(); + let passed = rate_limiter.check(ActionType::Message, ip, now); + assert_eq!(passed, expected_to_pass); + } + + // Expect `remove_full_buckets` to remove everything when called 2 seconds later now.secs += 2; - rate_limiter.remove_older_than(std::time::Duration::from_secs(1), now); + rate_limiter.remove_full_buckets(now); assert!(rate_limiter.ipv4_buckets.is_empty()); assert!(rate_limiter.ipv6_buckets.is_empty()); } diff --git a/src/lib.rs b/src/lib.rs index c093faaca..2df231dd5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -156,7 +156,7 @@ pub async fn start_lemmy_server(args: CmdArgs) -> Result<(), LemmyError> { // Set up the rate limiter let rate_limit_config = local_site_rate_limit_to_rate_limit_config(&site_view.local_site_rate_limit); - let rate_limit_cell = RateLimitCell::new(rate_limit_config).await; + let rate_limit_cell = RateLimitCell::new(rate_limit_config); println!( "Starting http server at {}:{}", @@ -298,7 +298,7 @@ fn create_http_server( .expect("Should always be buildable"); let context: LemmyContext = federation_config.deref().clone(); - let rate_limit_cell = federation_config.settings_updated_channel().clone(); + let rate_limit_cell = federation_config.rate_limit_cell().clone(); let self_origin = settings.get_protocol_and_hostname(); // Create Http server with websocket support let server = HttpServer::new(move || { diff --git a/src/scheduled_tasks.rs b/src/scheduled_tasks.rs index 99dd16829..8db74ef9d 100644 --- a/src/scheduled_tasks.rs +++ b/src/scheduled_tasks.rs @@ -78,17 +78,6 @@ pub async fn setup(context: LemmyContext) -> Result<(), LemmyError> { } }); - let context_1 = context.clone(); - // Remove old rate limit buckets after 1 to 2 hours of inactivity - scheduler.every(CTimeUnits::hour(1)).run(move || { - let context = context_1.clone(); - - async move { - let hour = Duration::from_secs(3600); - context.settings_updated_channel().remove_older_than(hour); - } - }); - let context_1 = context.clone(); // Overwrite deleted & removed posts and comments every day scheduler.every(CTimeUnits::days(1)).run(move || { diff --git a/src/session_middleware.rs b/src/session_middleware.rs index ae82cd44d..f50e0eccd 100644 --- a/src/session_middleware.rs +++ b/src/session_middleware.rs @@ -112,7 +112,7 @@ mod tests { traits::Crud, utils::build_db_pool_for_tests, }; - use lemmy_utils::rate_limit::{RateLimitCell, RateLimitConfig}; + use lemmy_utils::rate_limit::RateLimitCell; use reqwest::Client; use reqwest_middleware::ClientBuilder; use serial_test::serial; @@ -131,9 +131,7 @@ mod tests { pool_.clone(), ClientBuilder::new(Client::default()).build(), secret, - RateLimitCell::new(RateLimitConfig::builder().build()) - .await - .clone(), + RateLimitCell::with_test_config(), ); let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()) From 9ef28eb53bf91bc9f6c4ef5c19291ca8386d6f69 Mon Sep 17 00:00:00 2001 From: Nutomic Date: Thu, 19 Oct 2023 17:57:39 +0200 Subject: [PATCH 19/24] Remove unused static (#4061) --- crates/apub/src/activities/mod.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/crates/apub/src/activities/mod.rs b/crates/apub/src/activities/mod.rs index 936af8ad7..83e2caeb2 100644 --- a/crates/apub/src/activities/mod.rs +++ b/crates/apub/src/activities/mod.rs @@ -44,7 +44,7 @@ use lemmy_db_schema::source::{ use lemmy_db_views_actor::structs::{CommunityPersonBanView, CommunityView}; use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType, LemmyResult}; use serde::Serialize; -use std::{ops::Deref, time::Duration}; +use std::ops::Deref; use tracing::info; use url::{ParseError, Url}; use uuid::Uuid; @@ -56,10 +56,6 @@ pub mod deletion; pub mod following; pub mod voting; -/// Amount of time that the list of dead instances is cached. This is only updated once a day, -/// so there is no harm in caching it for a longer time. -pub static DEAD_INSTANCE_LIST_CACHE_DURATION: Duration = Duration::from_secs(30 * 60); - /// Checks that the specified Url actually identifies a Person (by fetching it), and that the person /// doesn't have a site ban. #[tracing::instrument(skip_all)] From 8deb4e5752cb9482d8082df559d81b9e1c573a94 Mon Sep 17 00:00:00 2001 From: Dessalines Date: Thu, 19 Oct 2023 13:21:05 -0400 Subject: [PATCH 20/24] Convert more responses to SuccessResponse. Fixes #2860 (#4058) * Convert more responses to SuccessResponse. Fixes #2860 * Upgrading lemmy-js-client. --- api_tests/package.json | 2 +- api_tests/src/community.spec.ts | 3 +- api_tests/src/post.spec.ts | 8 ++--- api_tests/src/shared.ts | 15 +++----- api_tests/yarn.lock | 8 ++--- crates/api/src/community/add_mod.rs | 13 +++---- crates/api/src/community/ban.rs | 10 +++--- crates/api/src/community/block.rs | 10 +++--- crates/api/src/local_user/add_admin.rs | 13 ++----- crates/api/src/local_user/ban_person.rs | 10 +++--- crates/api/src/local_user/block.rs | 14 ++------ .../notifications/mark_reply_read.rs | 15 ++------ crates/api/src/site/block.rs | 11 ++---- crates/api/src/site/purge/comment.rs | 7 ++-- crates/api/src/site/purge/community.rs | 7 ++-- crates/api/src/site/purge/person.rs | 7 ++-- crates/api/src/site/purge/post.rs | 7 ++-- crates/api_common/src/community.rs | 29 +--------------- crates/api_common/src/custom_emoji.rs | 9 ----- crates/api_common/src/person.rs | 34 ------------------- crates/api_common/src/site.rs | 16 --------- crates/api_crud/src/custom_emoji/delete.rs | 12 +++---- scripts/test.sh | 3 ++ 23 files changed, 69 insertions(+), 194 deletions(-) diff --git a/api_tests/package.json b/api_tests/package.json index 84e5d3df5..892484709 100644 --- a/api_tests/package.json +++ b/api_tests/package.json @@ -19,7 +19,7 @@ "eslint": "^8.51.0", "eslint-plugin-prettier": "^5.0.1", "jest": "^29.5.0", - "lemmy-js-client": "0.19.0-rc.12", + "lemmy-js-client": "0.19.0-alpha.12", "prettier": "^3.0.0", "ts-jest": "^29.1.0", "typescript": "^5.0.4" diff --git a/api_tests/src/community.spec.ts b/api_tests/src/community.spec.ts index 2c97d629f..a67cd693a 100644 --- a/api_tests/src/community.spec.ts +++ b/api_tests/src/community.spec.ts @@ -25,7 +25,6 @@ import { getCommunityByName, blockInstance, waitUntil, - delay, alphaUrl, delta, betaAllowedInstances, @@ -241,7 +240,7 @@ test("Admin actions in remote community are not federated to origin", async () = true, true, ); - expect(banRes.banned).toBe(true); + expect(banRes.success).toBe(true); // ban doesnt federate to community's origin instance alpha let alphaPost = (await resolvePost(alpha, gammaPost.post)).post; diff --git a/api_tests/src/post.spec.ts b/api_tests/src/post.spec.ts index 8c1f22226..f9af236ce 100644 --- a/api_tests/src/post.spec.ts +++ b/api_tests/src/post.spec.ts @@ -415,7 +415,7 @@ test("Enforce site ban for federated user", async () => { true, true, ); - expect(banAlpha.banned).toBe(true); + expect(banAlpha.success).toBe(true); // alpha ban should be federated to beta let alphaUserOnBeta1 = await waitUntil( @@ -437,7 +437,7 @@ test("Enforce site ban for federated user", async () => { false, false, ); - expect(unBanAlpha.banned).toBe(false); + expect(unBanAlpha.success).toBe(true); // Login gets invalidated by ban, need to login again if (!alphaUserPerson) { @@ -479,7 +479,7 @@ test.skip("Enforce community ban for federated user", async () => { true, true, ); - expect(banAlpha.banned).toBe(true); + expect(banAlpha.success).toBe(true); // ensure that the post by alpha got removed await expect(getPost(alpha, searchBeta1.posts[0].post.id)).rejects.toBe( @@ -499,7 +499,7 @@ test.skip("Enforce community ban for federated user", async () => { false, false, ); - expect(unBanAlpha.banned).toBe(false); + expect(unBanAlpha.success).toBe(true); let postRes3 = await createPost(alpha, betaCommunity.community.id); expect(postRes3.post_view.post).toBeDefined(); expect(postRes3.post_view.community.local).toBe(false); diff --git a/api_tests/src/shared.ts b/api_tests/src/shared.ts index e4dabb1d4..fce25adc2 100644 --- a/api_tests/src/shared.ts +++ b/api_tests/src/shared.ts @@ -1,12 +1,12 @@ import { BlockInstance, - BlockInstanceResponse, GetReplies, GetRepliesResponse, GetUnreadCountResponse, InstanceId, LemmyHttp, PostView, + SuccessResponse, } from "lemmy-js-client"; import { CreatePost } from "lemmy-js-client/dist/types/CreatePost"; import { DeletePost } from "lemmy-js-client/dist/types/DeletePost"; @@ -27,9 +27,7 @@ import { ResolveObjectResponse } from "lemmy-js-client/dist/types/ResolveObjectR import { Search } from "lemmy-js-client/dist/types/Search"; import { SearchResponse } from "lemmy-js-client/dist/types/SearchResponse"; import { Comment } from "lemmy-js-client/dist/types/Comment"; -import { BanPersonResponse } from "lemmy-js-client/dist/types/BanPersonResponse"; import { BanPerson } from "lemmy-js-client/dist/types/BanPerson"; -import { BanFromCommunityResponse } from "lemmy-js-client/dist/types/BanFromCommunityResponse"; import { BanFromCommunity } from "lemmy-js-client/dist/types/BanFromCommunity"; import { CommunityResponse } from "lemmy-js-client/dist/types/CommunityResponse"; import { FollowCommunity } from "lemmy-js-client/dist/types/FollowCommunity"; @@ -55,7 +53,6 @@ import { Register } from "lemmy-js-client/dist/types/Register"; import { SaveUserSettings } from "lemmy-js-client/dist/types/SaveUserSettings"; import { DeleteAccount } from "lemmy-js-client/dist/types/DeleteAccount"; import { GetSiteResponse } from "lemmy-js-client/dist/types/GetSiteResponse"; -import { DeleteAccountResponse } from "lemmy-js-client/dist/types/DeleteAccountResponse"; import { PrivateMessagesResponse } from "lemmy-js-client/dist/types/PrivateMessagesResponse"; import { GetPrivateMessages } from "lemmy-js-client/dist/types/GetPrivateMessages"; import { PostReportResponse } from "lemmy-js-client/dist/types/PostReportResponse"; @@ -385,7 +382,7 @@ export async function banPersonFromSite( person_id: number, ban: boolean, remove_data: boolean, -): Promise { +): Promise { // Make sure lemmy-beta/c/main is cached on lemmy_alpha let form: BanPerson = { person_id, @@ -401,7 +398,7 @@ export async function banPersonFromCommunity( community_id: number, remove_data: boolean, ban: boolean, -): Promise { +): Promise { let form: BanFromCommunity = { person_id, community_id, @@ -689,9 +686,7 @@ export async function getPersonDetails( return api.getPersonDetails(form); } -export async function deleteUser( - api: LemmyHttp, -): Promise { +export async function deleteUser(api: LemmyHttp): Promise { let form: DeleteAccount = { delete_content: true, password, @@ -788,7 +783,7 @@ export function blockInstance( api: LemmyHttp, instance_id: InstanceId, block: boolean, -): Promise { +): Promise { let form: BlockInstance = { instance_id, block, diff --git a/api_tests/yarn.lock b/api_tests/yarn.lock index 56764e096..f528a9144 100644 --- a/api_tests/yarn.lock +++ b/api_tests/yarn.lock @@ -2275,10 +2275,10 @@ kleur@^3.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== -lemmy-js-client@0.19.0-rc.12: - version "0.19.0-rc.12" - resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.19.0-rc.12.tgz#e3bd4e21b1966d583ab790ef70ece8394b012b48" - integrity sha512-1iu2fW9vlb3TrI+QR/ODP3+5pWZB0rUqL1wH09IzomDXohCqoQvfmXpwArmgF4Eq8GZgjkcfeMDC2gMrfw/i7Q== +lemmy-js-client@0.19.0-alpha.12: + version "0.19.0-alpha.12" + resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.19.0-alpha.12.tgz#0f98743483b3859414e7accce905105a7fc6df78" + integrity sha512-4SicZRNxZpLAxrjP54eRJmFHJ2AjNbWJv3PuTp2g6tkLQPUukDr8RsEOoDXDoIoqadgR3B5z1ujnuTtQrVrFKg== dependencies: cross-fetch "^3.1.5" form-data "^4.0.0" diff --git a/crates/api/src/community/add_mod.rs b/crates/api/src/community/add_mod.rs index 9d055c654..e8bf2c19f 100644 --- a/crates/api/src/community/add_mod.rs +++ b/crates/api/src/community/add_mod.rs @@ -1,10 +1,11 @@ use activitypub_federation::config::Data; use actix_web::web::Json; use lemmy_api_common::{ - community::{AddModToCommunity, AddModToCommunityResponse}, + community::AddModToCommunity, context::LemmyContext, send_activity::{ActivityChannel, SendActivityData}, utils::check_community_mod_action, + SuccessResponse, }; use lemmy_db_schema::{ source::{ @@ -14,7 +15,6 @@ use lemmy_db_schema::{ traits::{Crud, Joinable}, }; use lemmy_db_views::structs::LocalUserView; -use lemmy_db_views_actor::structs::CommunityModeratorView; use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; #[tracing::instrument(skip(context))] @@ -22,7 +22,7 @@ pub async fn add_mod_to_community( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> Result, LemmyError> { let community_id = data.community_id; // Verify that only mods or admins can add mod @@ -63,11 +63,6 @@ pub async fn add_mod_to_community( ModAddCommunity::create(&mut context.pool(), &form).await?; - // Note: in case a remote mod is added, this returns the old moderators list, it will only get - // updated once we receive an activity from the community (like `Announce/Add/Moderator`) - let community_id = data.community_id; - let moderators = CommunityModeratorView::for_community(&mut context.pool(), community_id).await?; - ActivityChannel::submit_activity( SendActivityData::AddModToCommunity( local_user_view.person, @@ -79,5 +74,5 @@ pub async fn add_mod_to_community( ) .await?; - Ok(Json(AddModToCommunityResponse { moderators })) + Ok(Json(SuccessResponse::default())) } diff --git a/crates/api/src/community/ban.rs b/crates/api/src/community/ban.rs index f662c4a08..8e315568d 100644 --- a/crates/api/src/community/ban.rs +++ b/crates/api/src/community/ban.rs @@ -1,10 +1,11 @@ use activitypub_federation::config::Data; use actix_web::web::Json; use lemmy_api_common::{ - community::{BanFromCommunity, BanFromCommunityResponse}, + community::BanFromCommunity, context::LemmyContext, send_activity::{ActivityChannel, SendActivityData}, utils::{check_community_mod_action, check_expire_time, remove_user_data_in_community}, + SuccessResponse, }; use lemmy_db_schema::{ source::{ @@ -30,7 +31,7 @@ pub async fn ban_from_community( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> Result, LemmyError> { let banned_person_id = data.person_id; let remove_data = data.remove_data.unwrap_or(false); let expires = check_expire_time(data.expires)?; @@ -102,8 +103,5 @@ pub async fn ban_from_community( ) .await?; - Ok(Json(BanFromCommunityResponse { - person_view, - banned: data.ban, - })) + Ok(Json(SuccessResponse::default())) } diff --git a/crates/api/src/community/block.rs b/crates/api/src/community/block.rs index fd4a5a01b..3c4e7ed9c 100644 --- a/crates/api/src/community/block.rs +++ b/crates/api/src/community/block.rs @@ -1,9 +1,10 @@ use activitypub_federation::config::Data; use actix_web::web::Json; use lemmy_api_common::{ - community::{BlockCommunity, BlockCommunityResponse}, + community::BlockCommunity, context::LemmyContext, send_activity::{ActivityChannel, SendActivityData}, + SuccessResponse, }; use lemmy_db_schema::{ source::{ @@ -21,7 +22,7 @@ pub async fn block_community( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> Result, LemmyError> { let community_id = data.community_id; let person_id = local_user_view.person.id; let community_block_form = CommunityBlockForm { @@ -63,8 +64,5 @@ pub async fn block_community( ) .await?; - Ok(Json(BlockCommunityResponse { - blocked: data.block, - community_view, - })) + Ok(Json(SuccessResponse::default())) } diff --git a/crates/api/src/local_user/add_admin.rs b/crates/api/src/local_user/add_admin.rs index 502335876..0fc9989b9 100644 --- a/crates/api/src/local_user/add_admin.rs +++ b/crates/api/src/local_user/add_admin.rs @@ -1,9 +1,5 @@ use actix_web::web::{Data, Json}; -use lemmy_api_common::{ - context::LemmyContext, - person::{AddAdmin, AddAdminResponse}, - utils::is_admin, -}; +use lemmy_api_common::{context::LemmyContext, person::AddAdmin, utils::is_admin, SuccessResponse}; use lemmy_db_schema::{ source::{ local_user::{LocalUser, LocalUserUpdateForm}, @@ -12,7 +8,6 @@ use lemmy_db_schema::{ traits::Crud, }; use lemmy_db_views::structs::LocalUserView; -use lemmy_db_views_actor::structs::PersonView; use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; #[tracing::instrument(skip(context))] @@ -20,7 +15,7 @@ pub async fn add_admin( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> Result, LemmyError> { // Make sure user is an admin is_admin(&local_user_view)?; @@ -49,7 +44,5 @@ pub async fn add_admin( ModAdd::create(&mut context.pool(), &form).await?; - let admins = PersonView::admins(&mut context.pool()).await?; - - Ok(Json(AddAdminResponse { admins })) + Ok(Json(SuccessResponse::default())) } diff --git a/crates/api/src/local_user/ban_person.rs b/crates/api/src/local_user/ban_person.rs index d7c47e619..caa30465d 100644 --- a/crates/api/src/local_user/ban_person.rs +++ b/crates/api/src/local_user/ban_person.rs @@ -2,9 +2,10 @@ use activitypub_federation::config::Data; use actix_web::web::Json; use lemmy_api_common::{ context::LemmyContext, - person::{BanPerson, BanPersonResponse}, + person::BanPerson, send_activity::{ActivityChannel, SendActivityData}, utils::{check_expire_time, is_admin, remove_user_data}, + SuccessResponse, }; use lemmy_db_schema::{ source::{ @@ -26,7 +27,7 @@ pub async fn ban_from_site( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> Result, LemmyError> { // Make sure user is an admin is_admin(&local_user_view)?; @@ -81,8 +82,5 @@ pub async fn ban_from_site( ) .await?; - Ok(Json(BanPersonResponse { - person_view, - banned: data.ban, - })) + Ok(Json(SuccessResponse::default())) } diff --git a/crates/api/src/local_user/block.rs b/crates/api/src/local_user/block.rs index cb345616b..2524c0d8e 100644 --- a/crates/api/src/local_user/block.rs +++ b/crates/api/src/local_user/block.rs @@ -1,14 +1,10 @@ use actix_web::web::{Data, Json}; -use lemmy_api_common::{ - context::LemmyContext, - person::{BlockPerson, BlockPersonResponse}, -}; +use lemmy_api_common::{context::LemmyContext, person::BlockPerson, SuccessResponse}; use lemmy_db_schema::{ source::person_block::{PersonBlock, PersonBlockForm}, traits::Blockable, }; use lemmy_db_views::structs::LocalUserView; -use lemmy_db_views_actor::structs::PersonView; use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; #[tracing::instrument(skip(context))] @@ -16,7 +12,7 @@ pub async fn block_person( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> Result, LemmyError> { let target_id = data.person_id; let person_id = local_user_view.person.id; @@ -45,9 +41,5 @@ pub async fn block_person( .with_lemmy_type(LemmyErrorType::PersonBlockAlreadyExists)?; } - let person_view = PersonView::read(&mut context.pool(), target_id).await?; - Ok(Json(BlockPersonResponse { - person_view, - blocked: data.block, - })) + Ok(Json(SuccessResponse::default())) } diff --git a/crates/api/src/local_user/notifications/mark_reply_read.rs b/crates/api/src/local_user/notifications/mark_reply_read.rs index f7b259c94..cc8a9774e 100644 --- a/crates/api/src/local_user/notifications/mark_reply_read.rs +++ b/crates/api/src/local_user/notifications/mark_reply_read.rs @@ -1,14 +1,10 @@ use actix_web::web::{Data, Json}; -use lemmy_api_common::{ - context::LemmyContext, - person::{CommentReplyResponse, MarkCommentReplyAsRead}, -}; +use lemmy_api_common::{context::LemmyContext, person::MarkCommentReplyAsRead, SuccessResponse}; use lemmy_db_schema::{ source::comment_reply::{CommentReply, CommentReplyUpdateForm}, traits::Crud, }; use lemmy_db_views::structs::LocalUserView; -use lemmy_db_views_actor::structs::CommentReplyView; use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; #[tracing::instrument(skip(context))] @@ -16,7 +12,7 @@ pub async fn mark_reply_as_read( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> Result, LemmyError> { let comment_reply_id = data.comment_reply_id; let read_comment_reply = CommentReply::read(&mut context.pool(), comment_reply_id).await?; @@ -35,10 +31,5 @@ pub async fn mark_reply_as_read( .await .with_lemmy_type(LemmyErrorType::CouldntUpdateComment)?; - let comment_reply_id = read_comment_reply.id; - let person_id = local_user_view.person.id; - let comment_reply_view = - CommentReplyView::read(&mut context.pool(), comment_reply_id, Some(person_id)).await?; - - Ok(Json(CommentReplyResponse { comment_reply_view })) + Ok(Json(SuccessResponse::default())) } diff --git a/crates/api/src/site/block.rs b/crates/api/src/site/block.rs index be48e8ce8..6cf220038 100644 --- a/crates/api/src/site/block.rs +++ b/crates/api/src/site/block.rs @@ -1,9 +1,6 @@ use activitypub_federation::config::Data; use actix_web::web::Json; -use lemmy_api_common::{ - context::LemmyContext, - site::{BlockInstance, BlockInstanceResponse}, -}; +use lemmy_api_common::{context::LemmyContext, site::BlockInstance, SuccessResponse}; use lemmy_db_schema::{ source::instance_block::{InstanceBlock, InstanceBlockForm}, traits::Blockable, @@ -16,7 +13,7 @@ pub async fn block_instance( data: Json, local_user_view: LocalUserView, context: Data, -) -> Result, LemmyError> { +) -> Result, LemmyError> { let instance_id = data.instance_id; let person_id = local_user_view.person.id; let instance_block_form = InstanceBlockForm { @@ -34,7 +31,5 @@ pub async fn block_instance( .with_lemmy_type(LemmyErrorType::InstanceBlockAlreadyExists)?; } - Ok(Json(BlockInstanceResponse { - blocked: data.block, - })) + Ok(Json(SuccessResponse::default())) } diff --git a/crates/api/src/site/purge/comment.rs b/crates/api/src/site/purge/comment.rs index 1537bc416..aa55dd3c9 100644 --- a/crates/api/src/site/purge/comment.rs +++ b/crates/api/src/site/purge/comment.rs @@ -1,8 +1,9 @@ use actix_web::web::{Data, Json}; use lemmy_api_common::{ context::LemmyContext, - site::{PurgeComment, PurgeItemResponse}, + site::PurgeComment, utils::is_admin, + SuccessResponse, }; use lemmy_db_schema::{ source::{ @@ -19,7 +20,7 @@ pub async fn purge_comment( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> Result, LemmyError> { // Only let admin purge an item is_admin(&local_user_view)?; @@ -43,5 +44,5 @@ pub async fn purge_comment( AdminPurgeComment::create(&mut context.pool(), &form).await?; - Ok(Json(PurgeItemResponse { success: true })) + Ok(Json(SuccessResponse::default())) } diff --git a/crates/api/src/site/purge/community.rs b/crates/api/src/site/purge/community.rs index 6ca30125d..6b307a06e 100644 --- a/crates/api/src/site/purge/community.rs +++ b/crates/api/src/site/purge/community.rs @@ -2,8 +2,9 @@ use actix_web::web::{Data, Json}; use lemmy_api_common::{ context::LemmyContext, request::purge_image_from_pictrs, - site::{PurgeCommunity, PurgeItemResponse}, + site::PurgeCommunity, utils::{is_admin, purge_image_posts_for_community}, + SuccessResponse, }; use lemmy_db_schema::{ source::{ @@ -20,7 +21,7 @@ pub async fn purge_community( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> Result, LemmyError> { // Only let admin purge an item is_admin(&local_user_view)?; @@ -49,5 +50,5 @@ pub async fn purge_community( AdminPurgeCommunity::create(&mut context.pool(), &form).await?; - Ok(Json(PurgeItemResponse { success: true })) + Ok(Json(SuccessResponse::default())) } diff --git a/crates/api/src/site/purge/person.rs b/crates/api/src/site/purge/person.rs index 96b8f7859..c59e06931 100644 --- a/crates/api/src/site/purge/person.rs +++ b/crates/api/src/site/purge/person.rs @@ -2,8 +2,9 @@ use actix_web::web::{Data, Json}; use lemmy_api_common::{ context::LemmyContext, request::delete_image_from_pictrs, - site::{PurgeItemResponse, PurgePerson}, + site::PurgePerson, utils::is_admin, + SuccessResponse, }; use lemmy_db_schema::{ source::{ @@ -21,7 +22,7 @@ pub async fn purge_person( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> Result, LemmyError> { // Only let admin purge an item is_admin(&local_user_view)?; @@ -48,5 +49,5 @@ pub async fn purge_person( AdminPurgePerson::create(&mut context.pool(), &form).await?; - Ok(Json(PurgeItemResponse { success: true })) + Ok(Json(SuccessResponse::default())) } diff --git a/crates/api/src/site/purge/post.rs b/crates/api/src/site/purge/post.rs index a7469aa90..68ef76001 100644 --- a/crates/api/src/site/purge/post.rs +++ b/crates/api/src/site/purge/post.rs @@ -2,8 +2,9 @@ use actix_web::web::{Data, Json}; use lemmy_api_common::{ context::LemmyContext, request::purge_image_from_pictrs, - site::{PurgeItemResponse, PurgePost}, + site::PurgePost, utils::is_admin, + SuccessResponse, }; use lemmy_db_schema::{ source::{ @@ -20,7 +21,7 @@ pub async fn purge_post( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> Result, LemmyError> { // Only let admin purge an item is_admin(&local_user_view)?; @@ -51,5 +52,5 @@ pub async fn purge_post( AdminPurgePost::create(&mut context.pool(), &form).await?; - Ok(Json(PurgeItemResponse { success: true })) + Ok(Json(SuccessResponse::default())) } diff --git a/crates/api_common/src/community.rs b/crates/api_common/src/community.rs index 8e87ab750..dab69e711 100644 --- a/crates/api_common/src/community.rs +++ b/crates/api_common/src/community.rs @@ -4,7 +4,7 @@ use lemmy_db_schema::{ ListingType, SortType, }; -use lemmy_db_views_actor::structs::{CommunityModeratorView, CommunityView, PersonView}; +use lemmy_db_views_actor::structs::{CommunityModeratorView, CommunityView}; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; #[cfg(feature = "full")] @@ -100,15 +100,6 @@ pub struct BanFromCommunity { pub expires: Option, } -#[derive(Debug, Serialize, Deserialize, Clone)] -#[cfg_attr(feature = "full", derive(TS))] -#[cfg_attr(feature = "full", ts(export))] -/// The response for banning a user from a community. -pub struct BanFromCommunityResponse { - pub person_view: PersonView, - pub banned: bool, -} - #[derive(Debug, Serialize, Deserialize, Clone, Default)] #[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", ts(export))] @@ -119,14 +110,6 @@ pub struct AddModToCommunity { pub added: bool, } -#[derive(Debug, Serialize, Deserialize, Clone)] -#[cfg_attr(feature = "full", derive(TS))] -#[cfg_attr(feature = "full", ts(export))] -/// The response of adding a moderator to a community. -pub struct AddModToCommunityResponse { - pub moderators: Vec, -} - #[skip_serializing_none] #[derive(Debug, Serialize, Deserialize, Clone, Default)] #[cfg_attr(feature = "full", derive(TS))] @@ -200,16 +183,6 @@ pub struct BlockCommunity { pub block: bool, } -#[skip_serializing_none] -#[derive(Debug, Serialize, Deserialize, Clone)] -#[cfg_attr(feature = "full", derive(TS))] -#[cfg_attr(feature = "full", ts(export))] -/// The block community response. -pub struct BlockCommunityResponse { - pub community_view: CommunityView, - pub blocked: bool, -} - #[derive(Debug, Serialize, Deserialize, Clone, Default)] #[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", ts(export))] diff --git a/crates/api_common/src/custom_emoji.rs b/crates/api_common/src/custom_emoji.rs index 83248dc91..d2900853e 100644 --- a/crates/api_common/src/custom_emoji.rs +++ b/crates/api_common/src/custom_emoji.rs @@ -39,15 +39,6 @@ pub struct DeleteCustomEmoji { pub id: CustomEmojiId, } -#[derive(Serialize, Deserialize, Clone)] -#[cfg_attr(feature = "full", derive(TS))] -#[cfg_attr(feature = "full", ts(export))] -/// The response for deleting a custom emoji. -pub struct DeleteCustomEmojiResponse { - pub id: CustomEmojiId, - pub success: bool, -} - #[derive(Debug, Serialize, Deserialize, Clone)] #[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", ts(export))] diff --git a/crates/api_common/src/person.rs b/crates/api_common/src/person.rs index c067c3799..9642821c4 100644 --- a/crates/api_common/src/person.rs +++ b/crates/api_common/src/person.rs @@ -193,14 +193,6 @@ pub struct AddAdmin { pub added: bool, } -#[derive(Debug, Serialize, Deserialize, Clone)] -#[cfg_attr(feature = "full", derive(TS))] -#[cfg_attr(feature = "full", ts(export))] -/// The response of current admins. -pub struct AddAdminResponse { - pub admins: Vec, -} - #[skip_serializing_none] #[derive(Debug, Serialize, Deserialize, Clone, Default)] #[cfg_attr(feature = "full", derive(TS))] @@ -224,15 +216,6 @@ pub struct BannedPersonsResponse { pub banned: Vec, } -#[derive(Debug, Serialize, Deserialize, Clone)] -#[cfg_attr(feature = "full", derive(TS))] -#[cfg_attr(feature = "full", ts(export))] -/// A response for a banned person. -pub struct BanPersonResponse { - pub person_view: PersonView, - pub banned: bool, -} - #[derive(Debug, Serialize, Deserialize, Clone, Default)] #[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", ts(export))] @@ -242,15 +225,6 @@ pub struct BlockPerson { pub block: bool, } -#[derive(Debug, Serialize, Deserialize, Clone)] -#[cfg_attr(feature = "full", derive(TS))] -#[cfg_attr(feature = "full", ts(export))] -/// The response for a person block. -pub struct BlockPersonResponse { - pub person_view: PersonView, - pub blocked: bool, -} - #[skip_serializing_none] #[derive(Debug, Serialize, Deserialize, Clone, Default)] #[cfg_attr(feature = "full", derive(TS))] @@ -318,14 +292,6 @@ pub struct MarkCommentReplyAsRead { pub read: bool, } -#[derive(Debug, Serialize, Deserialize, Clone)] -#[cfg_attr(feature = "full", derive(TS))] -#[cfg_attr(feature = "full", ts(export))] -/// The response for a comment reply action. -pub struct CommentReplyResponse { - pub comment_reply_view: CommentReplyView, -} - #[derive(Debug, Serialize, Deserialize, Clone, Default)] #[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", ts(export))] diff --git a/crates/api_common/src/site.rs b/crates/api_common/src/site.rs index b047c6dd0..a66f20040 100644 --- a/crates/api_common/src/site.rs +++ b/crates/api_common/src/site.rs @@ -361,14 +361,6 @@ pub struct PurgeComment { pub reason: Option, } -#[derive(Serialize, Deserialize, Clone)] -#[cfg_attr(feature = "full", derive(TS))] -#[cfg_attr(feature = "full", ts(export))] -/// The response for purged items. -pub struct PurgeItemResponse { - pub success: bool, -} - #[skip_serializing_none] #[derive(Debug, Serialize, Deserialize, Clone, Default)] #[cfg_attr(feature = "full", derive(TS))] @@ -424,11 +416,3 @@ pub struct BlockInstance { pub instance_id: InstanceId, pub block: bool, } - -#[skip_serializing_none] -#[derive(Debug, Serialize, Deserialize, Clone)] -#[cfg_attr(feature = "full", derive(TS))] -#[cfg_attr(feature = "full", ts(export))] -pub struct BlockInstanceResponse { - pub blocked: bool, -} diff --git a/crates/api_crud/src/custom_emoji/delete.rs b/crates/api_crud/src/custom_emoji/delete.rs index 44cddd520..93c5f8d80 100644 --- a/crates/api_crud/src/custom_emoji/delete.rs +++ b/crates/api_crud/src/custom_emoji/delete.rs @@ -2,8 +2,9 @@ use activitypub_federation::config::Data; use actix_web::web::Json; use lemmy_api_common::{ context::LemmyContext, - custom_emoji::{DeleteCustomEmoji, DeleteCustomEmojiResponse}, + custom_emoji::DeleteCustomEmoji, utils::is_admin, + SuccessResponse, }; use lemmy_db_schema::source::custom_emoji::CustomEmoji; use lemmy_db_views::structs::LocalUserView; @@ -14,12 +15,11 @@ pub async fn delete_custom_emoji( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> Result, LemmyError> { // Make sure user is an admin is_admin(&local_user_view)?; + CustomEmoji::delete(&mut context.pool(), data.id).await?; - Ok(Json(DeleteCustomEmojiResponse { - id: data.id, - success: true, - })) + + Ok(Json(SuccessResponse::default())) } diff --git a/scripts/test.sh b/scripts/test.sh index 2a5efb30d..cdfbf7611 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -22,6 +22,9 @@ else cargo test --workspace --no-fail-fast fi +# Testing lemmy utils all features in particular (for ts-rs bindings) +cargo test -p lemmy_utils --all-features --no-fail-fast + # Add this to do printlns: -- --nocapture pg_ctl stop From c1db65c6e55cf179926f8ec1689aec94faa89da9 Mon Sep 17 00:00:00 2001 From: Dessalines Date: Thu, 19 Oct 2023 20:15:55 -0400 Subject: [PATCH 21/24] Fix up convert more responses to SuccessResponse (#4066) * Revert "Convert more responses to SuccessResponse. Fixes #2860 (#4058)" This reverts commit 8deb4e5752cb9482d8082df559d81b9e1c573a94. * Removing purgeitem and deletecustomemoji response. * Adding back in utils building. --- api_tests/package.json | 2 +- api_tests/src/community.spec.ts | 3 +- api_tests/src/post.spec.ts | 8 ++--- api_tests/src/shared.ts | 15 +++++--- api_tests/yarn.lock | 8 ++--- crates/api/src/community/add_mod.rs | 13 ++++--- crates/api/src/community/ban.rs | 10 +++--- crates/api/src/community/block.rs | 10 +++--- crates/api/src/local_user/add_admin.rs | 13 +++++-- crates/api/src/local_user/ban_person.rs | 10 +++--- crates/api/src/local_user/block.rs | 14 ++++++-- .../notifications/mark_reply_read.rs | 15 ++++++-- crates/api/src/site/block.rs | 11 ++++-- crates/api_common/src/community.rs | 29 +++++++++++++++- crates/api_common/src/person.rs | 34 +++++++++++++++++++ crates/api_common/src/site.rs | 8 +++++ 16 files changed, 159 insertions(+), 44 deletions(-) diff --git a/api_tests/package.json b/api_tests/package.json index 892484709..84e5d3df5 100644 --- a/api_tests/package.json +++ b/api_tests/package.json @@ -19,7 +19,7 @@ "eslint": "^8.51.0", "eslint-plugin-prettier": "^5.0.1", "jest": "^29.5.0", - "lemmy-js-client": "0.19.0-alpha.12", + "lemmy-js-client": "0.19.0-rc.12", "prettier": "^3.0.0", "ts-jest": "^29.1.0", "typescript": "^5.0.4" diff --git a/api_tests/src/community.spec.ts b/api_tests/src/community.spec.ts index a67cd693a..2c97d629f 100644 --- a/api_tests/src/community.spec.ts +++ b/api_tests/src/community.spec.ts @@ -25,6 +25,7 @@ import { getCommunityByName, blockInstance, waitUntil, + delay, alphaUrl, delta, betaAllowedInstances, @@ -240,7 +241,7 @@ test("Admin actions in remote community are not federated to origin", async () = true, true, ); - expect(banRes.success).toBe(true); + expect(banRes.banned).toBe(true); // ban doesnt federate to community's origin instance alpha let alphaPost = (await resolvePost(alpha, gammaPost.post)).post; diff --git a/api_tests/src/post.spec.ts b/api_tests/src/post.spec.ts index f9af236ce..8c1f22226 100644 --- a/api_tests/src/post.spec.ts +++ b/api_tests/src/post.spec.ts @@ -415,7 +415,7 @@ test("Enforce site ban for federated user", async () => { true, true, ); - expect(banAlpha.success).toBe(true); + expect(banAlpha.banned).toBe(true); // alpha ban should be federated to beta let alphaUserOnBeta1 = await waitUntil( @@ -437,7 +437,7 @@ test("Enforce site ban for federated user", async () => { false, false, ); - expect(unBanAlpha.success).toBe(true); + expect(unBanAlpha.banned).toBe(false); // Login gets invalidated by ban, need to login again if (!alphaUserPerson) { @@ -479,7 +479,7 @@ test.skip("Enforce community ban for federated user", async () => { true, true, ); - expect(banAlpha.success).toBe(true); + expect(banAlpha.banned).toBe(true); // ensure that the post by alpha got removed await expect(getPost(alpha, searchBeta1.posts[0].post.id)).rejects.toBe( @@ -499,7 +499,7 @@ test.skip("Enforce community ban for federated user", async () => { false, false, ); - expect(unBanAlpha.success).toBe(true); + expect(unBanAlpha.banned).toBe(false); let postRes3 = await createPost(alpha, betaCommunity.community.id); expect(postRes3.post_view.post).toBeDefined(); expect(postRes3.post_view.community.local).toBe(false); diff --git a/api_tests/src/shared.ts b/api_tests/src/shared.ts index fce25adc2..e4dabb1d4 100644 --- a/api_tests/src/shared.ts +++ b/api_tests/src/shared.ts @@ -1,12 +1,12 @@ import { BlockInstance, + BlockInstanceResponse, GetReplies, GetRepliesResponse, GetUnreadCountResponse, InstanceId, LemmyHttp, PostView, - SuccessResponse, } from "lemmy-js-client"; import { CreatePost } from "lemmy-js-client/dist/types/CreatePost"; import { DeletePost } from "lemmy-js-client/dist/types/DeletePost"; @@ -27,7 +27,9 @@ import { ResolveObjectResponse } from "lemmy-js-client/dist/types/ResolveObjectR import { Search } from "lemmy-js-client/dist/types/Search"; import { SearchResponse } from "lemmy-js-client/dist/types/SearchResponse"; import { Comment } from "lemmy-js-client/dist/types/Comment"; +import { BanPersonResponse } from "lemmy-js-client/dist/types/BanPersonResponse"; import { BanPerson } from "lemmy-js-client/dist/types/BanPerson"; +import { BanFromCommunityResponse } from "lemmy-js-client/dist/types/BanFromCommunityResponse"; import { BanFromCommunity } from "lemmy-js-client/dist/types/BanFromCommunity"; import { CommunityResponse } from "lemmy-js-client/dist/types/CommunityResponse"; import { FollowCommunity } from "lemmy-js-client/dist/types/FollowCommunity"; @@ -53,6 +55,7 @@ import { Register } from "lemmy-js-client/dist/types/Register"; import { SaveUserSettings } from "lemmy-js-client/dist/types/SaveUserSettings"; import { DeleteAccount } from "lemmy-js-client/dist/types/DeleteAccount"; import { GetSiteResponse } from "lemmy-js-client/dist/types/GetSiteResponse"; +import { DeleteAccountResponse } from "lemmy-js-client/dist/types/DeleteAccountResponse"; import { PrivateMessagesResponse } from "lemmy-js-client/dist/types/PrivateMessagesResponse"; import { GetPrivateMessages } from "lemmy-js-client/dist/types/GetPrivateMessages"; import { PostReportResponse } from "lemmy-js-client/dist/types/PostReportResponse"; @@ -382,7 +385,7 @@ export async function banPersonFromSite( person_id: number, ban: boolean, remove_data: boolean, -): Promise { +): Promise { // Make sure lemmy-beta/c/main is cached on lemmy_alpha let form: BanPerson = { person_id, @@ -398,7 +401,7 @@ export async function banPersonFromCommunity( community_id: number, remove_data: boolean, ban: boolean, -): Promise { +): Promise { let form: BanFromCommunity = { person_id, community_id, @@ -686,7 +689,9 @@ export async function getPersonDetails( return api.getPersonDetails(form); } -export async function deleteUser(api: LemmyHttp): Promise { +export async function deleteUser( + api: LemmyHttp, +): Promise { let form: DeleteAccount = { delete_content: true, password, @@ -783,7 +788,7 @@ export function blockInstance( api: LemmyHttp, instance_id: InstanceId, block: boolean, -): Promise { +): Promise { let form: BlockInstance = { instance_id, block, diff --git a/api_tests/yarn.lock b/api_tests/yarn.lock index f528a9144..56764e096 100644 --- a/api_tests/yarn.lock +++ b/api_tests/yarn.lock @@ -2275,10 +2275,10 @@ kleur@^3.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== -lemmy-js-client@0.19.0-alpha.12: - version "0.19.0-alpha.12" - resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.19.0-alpha.12.tgz#0f98743483b3859414e7accce905105a7fc6df78" - integrity sha512-4SicZRNxZpLAxrjP54eRJmFHJ2AjNbWJv3PuTp2g6tkLQPUukDr8RsEOoDXDoIoqadgR3B5z1ujnuTtQrVrFKg== +lemmy-js-client@0.19.0-rc.12: + version "0.19.0-rc.12" + resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.19.0-rc.12.tgz#e3bd4e21b1966d583ab790ef70ece8394b012b48" + integrity sha512-1iu2fW9vlb3TrI+QR/ODP3+5pWZB0rUqL1wH09IzomDXohCqoQvfmXpwArmgF4Eq8GZgjkcfeMDC2gMrfw/i7Q== dependencies: cross-fetch "^3.1.5" form-data "^4.0.0" diff --git a/crates/api/src/community/add_mod.rs b/crates/api/src/community/add_mod.rs index e8bf2c19f..9d055c654 100644 --- a/crates/api/src/community/add_mod.rs +++ b/crates/api/src/community/add_mod.rs @@ -1,11 +1,10 @@ use activitypub_federation::config::Data; use actix_web::web::Json; use lemmy_api_common::{ - community::AddModToCommunity, + community::{AddModToCommunity, AddModToCommunityResponse}, context::LemmyContext, send_activity::{ActivityChannel, SendActivityData}, utils::check_community_mod_action, - SuccessResponse, }; use lemmy_db_schema::{ source::{ @@ -15,6 +14,7 @@ use lemmy_db_schema::{ traits::{Crud, Joinable}, }; use lemmy_db_views::structs::LocalUserView; +use lemmy_db_views_actor::structs::CommunityModeratorView; use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; #[tracing::instrument(skip(context))] @@ -22,7 +22,7 @@ pub async fn add_mod_to_community( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> Result, LemmyError> { let community_id = data.community_id; // Verify that only mods or admins can add mod @@ -63,6 +63,11 @@ pub async fn add_mod_to_community( ModAddCommunity::create(&mut context.pool(), &form).await?; + // Note: in case a remote mod is added, this returns the old moderators list, it will only get + // updated once we receive an activity from the community (like `Announce/Add/Moderator`) + let community_id = data.community_id; + let moderators = CommunityModeratorView::for_community(&mut context.pool(), community_id).await?; + ActivityChannel::submit_activity( SendActivityData::AddModToCommunity( local_user_view.person, @@ -74,5 +79,5 @@ pub async fn add_mod_to_community( ) .await?; - Ok(Json(SuccessResponse::default())) + Ok(Json(AddModToCommunityResponse { moderators })) } diff --git a/crates/api/src/community/ban.rs b/crates/api/src/community/ban.rs index 8e315568d..f662c4a08 100644 --- a/crates/api/src/community/ban.rs +++ b/crates/api/src/community/ban.rs @@ -1,11 +1,10 @@ use activitypub_federation::config::Data; use actix_web::web::Json; use lemmy_api_common::{ - community::BanFromCommunity, + community::{BanFromCommunity, BanFromCommunityResponse}, context::LemmyContext, send_activity::{ActivityChannel, SendActivityData}, utils::{check_community_mod_action, check_expire_time, remove_user_data_in_community}, - SuccessResponse, }; use lemmy_db_schema::{ source::{ @@ -31,7 +30,7 @@ pub async fn ban_from_community( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> Result, LemmyError> { let banned_person_id = data.person_id; let remove_data = data.remove_data.unwrap_or(false); let expires = check_expire_time(data.expires)?; @@ -103,5 +102,8 @@ pub async fn ban_from_community( ) .await?; - Ok(Json(SuccessResponse::default())) + Ok(Json(BanFromCommunityResponse { + person_view, + banned: data.ban, + })) } diff --git a/crates/api/src/community/block.rs b/crates/api/src/community/block.rs index 3c4e7ed9c..fd4a5a01b 100644 --- a/crates/api/src/community/block.rs +++ b/crates/api/src/community/block.rs @@ -1,10 +1,9 @@ use activitypub_federation::config::Data; use actix_web::web::Json; use lemmy_api_common::{ - community::BlockCommunity, + community::{BlockCommunity, BlockCommunityResponse}, context::LemmyContext, send_activity::{ActivityChannel, SendActivityData}, - SuccessResponse, }; use lemmy_db_schema::{ source::{ @@ -22,7 +21,7 @@ pub async fn block_community( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> Result, LemmyError> { let community_id = data.community_id; let person_id = local_user_view.person.id; let community_block_form = CommunityBlockForm { @@ -64,5 +63,8 @@ pub async fn block_community( ) .await?; - Ok(Json(SuccessResponse::default())) + Ok(Json(BlockCommunityResponse { + blocked: data.block, + community_view, + })) } diff --git a/crates/api/src/local_user/add_admin.rs b/crates/api/src/local_user/add_admin.rs index 0fc9989b9..502335876 100644 --- a/crates/api/src/local_user/add_admin.rs +++ b/crates/api/src/local_user/add_admin.rs @@ -1,5 +1,9 @@ use actix_web::web::{Data, Json}; -use lemmy_api_common::{context::LemmyContext, person::AddAdmin, utils::is_admin, SuccessResponse}; +use lemmy_api_common::{ + context::LemmyContext, + person::{AddAdmin, AddAdminResponse}, + utils::is_admin, +}; use lemmy_db_schema::{ source::{ local_user::{LocalUser, LocalUserUpdateForm}, @@ -8,6 +12,7 @@ use lemmy_db_schema::{ traits::Crud, }; use lemmy_db_views::structs::LocalUserView; +use lemmy_db_views_actor::structs::PersonView; use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; #[tracing::instrument(skip(context))] @@ -15,7 +20,7 @@ pub async fn add_admin( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> Result, LemmyError> { // Make sure user is an admin is_admin(&local_user_view)?; @@ -44,5 +49,7 @@ pub async fn add_admin( ModAdd::create(&mut context.pool(), &form).await?; - Ok(Json(SuccessResponse::default())) + let admins = PersonView::admins(&mut context.pool()).await?; + + Ok(Json(AddAdminResponse { admins })) } diff --git a/crates/api/src/local_user/ban_person.rs b/crates/api/src/local_user/ban_person.rs index caa30465d..d7c47e619 100644 --- a/crates/api/src/local_user/ban_person.rs +++ b/crates/api/src/local_user/ban_person.rs @@ -2,10 +2,9 @@ use activitypub_federation::config::Data; use actix_web::web::Json; use lemmy_api_common::{ context::LemmyContext, - person::BanPerson, + person::{BanPerson, BanPersonResponse}, send_activity::{ActivityChannel, SendActivityData}, utils::{check_expire_time, is_admin, remove_user_data}, - SuccessResponse, }; use lemmy_db_schema::{ source::{ @@ -27,7 +26,7 @@ pub async fn ban_from_site( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> Result, LemmyError> { // Make sure user is an admin is_admin(&local_user_view)?; @@ -82,5 +81,8 @@ pub async fn ban_from_site( ) .await?; - Ok(Json(SuccessResponse::default())) + Ok(Json(BanPersonResponse { + person_view, + banned: data.ban, + })) } diff --git a/crates/api/src/local_user/block.rs b/crates/api/src/local_user/block.rs index 2524c0d8e..cb345616b 100644 --- a/crates/api/src/local_user/block.rs +++ b/crates/api/src/local_user/block.rs @@ -1,10 +1,14 @@ use actix_web::web::{Data, Json}; -use lemmy_api_common::{context::LemmyContext, person::BlockPerson, SuccessResponse}; +use lemmy_api_common::{ + context::LemmyContext, + person::{BlockPerson, BlockPersonResponse}, +}; use lemmy_db_schema::{ source::person_block::{PersonBlock, PersonBlockForm}, traits::Blockable, }; use lemmy_db_views::structs::LocalUserView; +use lemmy_db_views_actor::structs::PersonView; use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; #[tracing::instrument(skip(context))] @@ -12,7 +16,7 @@ pub async fn block_person( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> Result, LemmyError> { let target_id = data.person_id; let person_id = local_user_view.person.id; @@ -41,5 +45,9 @@ pub async fn block_person( .with_lemmy_type(LemmyErrorType::PersonBlockAlreadyExists)?; } - Ok(Json(SuccessResponse::default())) + let person_view = PersonView::read(&mut context.pool(), target_id).await?; + Ok(Json(BlockPersonResponse { + person_view, + blocked: data.block, + })) } diff --git a/crates/api/src/local_user/notifications/mark_reply_read.rs b/crates/api/src/local_user/notifications/mark_reply_read.rs index cc8a9774e..f7b259c94 100644 --- a/crates/api/src/local_user/notifications/mark_reply_read.rs +++ b/crates/api/src/local_user/notifications/mark_reply_read.rs @@ -1,10 +1,14 @@ use actix_web::web::{Data, Json}; -use lemmy_api_common::{context::LemmyContext, person::MarkCommentReplyAsRead, SuccessResponse}; +use lemmy_api_common::{ + context::LemmyContext, + person::{CommentReplyResponse, MarkCommentReplyAsRead}, +}; use lemmy_db_schema::{ source::comment_reply::{CommentReply, CommentReplyUpdateForm}, traits::Crud, }; use lemmy_db_views::structs::LocalUserView; +use lemmy_db_views_actor::structs::CommentReplyView; use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; #[tracing::instrument(skip(context))] @@ -12,7 +16,7 @@ pub async fn mark_reply_as_read( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> Result, LemmyError> { let comment_reply_id = data.comment_reply_id; let read_comment_reply = CommentReply::read(&mut context.pool(), comment_reply_id).await?; @@ -31,5 +35,10 @@ pub async fn mark_reply_as_read( .await .with_lemmy_type(LemmyErrorType::CouldntUpdateComment)?; - Ok(Json(SuccessResponse::default())) + let comment_reply_id = read_comment_reply.id; + let person_id = local_user_view.person.id; + let comment_reply_view = + CommentReplyView::read(&mut context.pool(), comment_reply_id, Some(person_id)).await?; + + Ok(Json(CommentReplyResponse { comment_reply_view })) } diff --git a/crates/api/src/site/block.rs b/crates/api/src/site/block.rs index 6cf220038..be48e8ce8 100644 --- a/crates/api/src/site/block.rs +++ b/crates/api/src/site/block.rs @@ -1,6 +1,9 @@ use activitypub_federation::config::Data; use actix_web::web::Json; -use lemmy_api_common::{context::LemmyContext, site::BlockInstance, SuccessResponse}; +use lemmy_api_common::{ + context::LemmyContext, + site::{BlockInstance, BlockInstanceResponse}, +}; use lemmy_db_schema::{ source::instance_block::{InstanceBlock, InstanceBlockForm}, traits::Blockable, @@ -13,7 +16,7 @@ pub async fn block_instance( data: Json, local_user_view: LocalUserView, context: Data, -) -> Result, LemmyError> { +) -> Result, LemmyError> { let instance_id = data.instance_id; let person_id = local_user_view.person.id; let instance_block_form = InstanceBlockForm { @@ -31,5 +34,7 @@ pub async fn block_instance( .with_lemmy_type(LemmyErrorType::InstanceBlockAlreadyExists)?; } - Ok(Json(SuccessResponse::default())) + Ok(Json(BlockInstanceResponse { + blocked: data.block, + })) } diff --git a/crates/api_common/src/community.rs b/crates/api_common/src/community.rs index dab69e711..8e87ab750 100644 --- a/crates/api_common/src/community.rs +++ b/crates/api_common/src/community.rs @@ -4,7 +4,7 @@ use lemmy_db_schema::{ ListingType, SortType, }; -use lemmy_db_views_actor::structs::{CommunityModeratorView, CommunityView}; +use lemmy_db_views_actor::structs::{CommunityModeratorView, CommunityView, PersonView}; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; #[cfg(feature = "full")] @@ -100,6 +100,15 @@ pub struct BanFromCommunity { pub expires: Option, } +#[derive(Debug, Serialize, Deserialize, Clone)] +#[cfg_attr(feature = "full", derive(TS))] +#[cfg_attr(feature = "full", ts(export))] +/// The response for banning a user from a community. +pub struct BanFromCommunityResponse { + pub person_view: PersonView, + pub banned: bool, +} + #[derive(Debug, Serialize, Deserialize, Clone, Default)] #[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", ts(export))] @@ -110,6 +119,14 @@ pub struct AddModToCommunity { pub added: bool, } +#[derive(Debug, Serialize, Deserialize, Clone)] +#[cfg_attr(feature = "full", derive(TS))] +#[cfg_attr(feature = "full", ts(export))] +/// The response of adding a moderator to a community. +pub struct AddModToCommunityResponse { + pub moderators: Vec, +} + #[skip_serializing_none] #[derive(Debug, Serialize, Deserialize, Clone, Default)] #[cfg_attr(feature = "full", derive(TS))] @@ -183,6 +200,16 @@ pub struct BlockCommunity { pub block: bool, } +#[skip_serializing_none] +#[derive(Debug, Serialize, Deserialize, Clone)] +#[cfg_attr(feature = "full", derive(TS))] +#[cfg_attr(feature = "full", ts(export))] +/// The block community response. +pub struct BlockCommunityResponse { + pub community_view: CommunityView, + pub blocked: bool, +} + #[derive(Debug, Serialize, Deserialize, Clone, Default)] #[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", ts(export))] diff --git a/crates/api_common/src/person.rs b/crates/api_common/src/person.rs index 9642821c4..c067c3799 100644 --- a/crates/api_common/src/person.rs +++ b/crates/api_common/src/person.rs @@ -193,6 +193,14 @@ pub struct AddAdmin { pub added: bool, } +#[derive(Debug, Serialize, Deserialize, Clone)] +#[cfg_attr(feature = "full", derive(TS))] +#[cfg_attr(feature = "full", ts(export))] +/// The response of current admins. +pub struct AddAdminResponse { + pub admins: Vec, +} + #[skip_serializing_none] #[derive(Debug, Serialize, Deserialize, Clone, Default)] #[cfg_attr(feature = "full", derive(TS))] @@ -216,6 +224,15 @@ pub struct BannedPersonsResponse { pub banned: Vec, } +#[derive(Debug, Serialize, Deserialize, Clone)] +#[cfg_attr(feature = "full", derive(TS))] +#[cfg_attr(feature = "full", ts(export))] +/// A response for a banned person. +pub struct BanPersonResponse { + pub person_view: PersonView, + pub banned: bool, +} + #[derive(Debug, Serialize, Deserialize, Clone, Default)] #[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", ts(export))] @@ -225,6 +242,15 @@ pub struct BlockPerson { pub block: bool, } +#[derive(Debug, Serialize, Deserialize, Clone)] +#[cfg_attr(feature = "full", derive(TS))] +#[cfg_attr(feature = "full", ts(export))] +/// The response for a person block. +pub struct BlockPersonResponse { + pub person_view: PersonView, + pub blocked: bool, +} + #[skip_serializing_none] #[derive(Debug, Serialize, Deserialize, Clone, Default)] #[cfg_attr(feature = "full", derive(TS))] @@ -292,6 +318,14 @@ pub struct MarkCommentReplyAsRead { pub read: bool, } +#[derive(Debug, Serialize, Deserialize, Clone)] +#[cfg_attr(feature = "full", derive(TS))] +#[cfg_attr(feature = "full", ts(export))] +/// The response for a comment reply action. +pub struct CommentReplyResponse { + pub comment_reply_view: CommentReplyView, +} + #[derive(Debug, Serialize, Deserialize, Clone, Default)] #[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", ts(export))] diff --git a/crates/api_common/src/site.rs b/crates/api_common/src/site.rs index a66f20040..d40729e35 100644 --- a/crates/api_common/src/site.rs +++ b/crates/api_common/src/site.rs @@ -416,3 +416,11 @@ pub struct BlockInstance { pub instance_id: InstanceId, pub block: bool, } + +#[skip_serializing_none] +#[derive(Debug, Serialize, Deserialize, Clone)] +#[cfg_attr(feature = "full", derive(TS))] +#[cfg_attr(feature = "full", ts(export))] +pub struct BlockInstanceResponse { + pub blocked: bool, +} From cae25486e40bbeada8e13c9b32f4e478017d6955 Mon Sep 17 00:00:00 2001 From: Dessalines Date: Thu, 19 Oct 2023 20:16:12 -0400 Subject: [PATCH 22/24] Adding ts-rs directives for login_token. (#4063) * Adding ts-rs directives for login_token. * Fixing ts-rs derive. --- crates/api/src/community/hide.rs | 8 ++++---- crates/api_common/src/community.rs | 1 - crates/db_schema/src/source/login_token.rs | 7 ++++++- src/api_routes_http.rs | 4 ++-- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/crates/api/src/community/hide.rs b/crates/api/src/community/hide.rs index dd8343927..27919a42b 100644 --- a/crates/api/src/community/hide.rs +++ b/crates/api/src/community/hide.rs @@ -1,11 +1,11 @@ use activitypub_federation::config::Data; use actix_web::web::Json; use lemmy_api_common::{ - build_response::build_community_response, - community::{CommunityResponse, HideCommunity}, + community::HideCommunity, context::LemmyContext, send_activity::{ActivityChannel, SendActivityData}, utils::is_admin, + SuccessResponse, }; use lemmy_db_schema::{ source::{ @@ -22,7 +22,7 @@ pub async fn hide_community( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> Result, LemmyError> { // Verify its a admin (only admin can hide or unhide it) is_admin(&local_user_view)?; @@ -51,5 +51,5 @@ pub async fn hide_community( ) .await?; - build_community_response(&context, local_user_view, community_id).await + Ok(Json(SuccessResponse::default())) } diff --git a/crates/api_common/src/community.rs b/crates/api_common/src/community.rs index 8e87ab750..1f4a94636 100644 --- a/crates/api_common/src/community.rs +++ b/crates/api_common/src/community.rs @@ -154,7 +154,6 @@ pub struct EditCommunity { #[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", ts(export))] /// Hide a community from the main view. -// TODO this should really be a part of edit community. And why does it contain a reason, that should be in the mod tables. pub struct HideCommunity { pub community_id: CommunityId, pub hidden: bool, diff --git a/crates/db_schema/src/source/login_token.rs b/crates/db_schema/src/source/login_token.rs index 008b96e04..45f74c41f 100644 --- a/crates/db_schema/src/source/login_token.rs +++ b/crates/db_schema/src/source/login_token.rs @@ -3,11 +3,16 @@ use crate::newtypes::LocalUserId; use crate::schema::login_token; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; +use serde_with::skip_serializing_none; +#[cfg(feature = "full")] +use ts_rs::TS; /// Stores data related to a specific user login session. +#[skip_serializing_none] #[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] -#[cfg_attr(feature = "full", derive(Queryable, Identifiable))] +#[cfg_attr(feature = "full", derive(Queryable, Identifiable, TS))] #[cfg_attr(feature = "full", diesel(table_name = login_token))] +#[cfg_attr(feature = "full", ts(export))] pub struct LoginToken { pub id: i32, /// Jwt token for this login diff --git a/src/api_routes_http.rs b/src/api_routes_http.rs index 26d6b9e8d..fb784b3b3 100644 --- a/src/api_routes_http.rs +++ b/src/api_routes_http.rs @@ -275,7 +275,7 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimitCell) { .route("/ban", web::post().to(ban_from_site)) .route("/banned", web::get().to(list_banned_users)) .route("/block", web::post().to(block_person)) - // Account actions. I don't like that they're in /user maybe /accounts + // TODO Account actions. I don't like that they're in /user maybe /accounts .route("/login", web::post().to(login)) .route("/logout", web::post().to(logout)) .route("/delete_account", web::post().to(delete_account)) @@ -284,7 +284,7 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimitCell) { "/password_change", web::post().to(change_password_after_reset), ) - // mark_all_as_read feels off being in this section as well + // TODO mark_all_as_read feels off being in this section as well .route( "/mark_all_as_read", web::post().to(mark_all_notifications_read), From 236c7e24fd27bd49169fbd4072dbb92cfc078498 Mon Sep 17 00:00:00 2001 From: Dessalines Date: Thu, 19 Oct 2023 20:16:23 -0400 Subject: [PATCH 23/24] Making mark post read fields optional. (#4055) * Making mark post read fields optional. * Remove unecessary & * Fix clippy. * Addressing PR comments. * serde(default) * Revert "serde(default)" This reverts commit d56afd3075a3baccb2b0eda1cc739406b83963aa. --------- Co-authored-by: Felix Ableitner --- crates/api/src/post/mark_read.rs | 13 ++++++++++--- crates/api_common/src/post.rs | 5 +++-- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/crates/api/src/post/mark_read.rs b/crates/api/src/post/mark_read.rs index a377b3c6c..a46e949fa 100644 --- a/crates/api/src/post/mark_read.rs +++ b/crates/api/src/post/mark_read.rs @@ -11,14 +11,21 @@ pub async fn mark_post_as_read( context: Data, local_user_view: LocalUserView, ) -> Result, LemmyError> { - let mut post_ids = data.post_ids.iter().cloned().collect::>(); - post_ids.insert(data.post_id); - let person_id = local_user_view.person.id; + let mut post_ids = HashSet::new(); + if let Some(post_ids_) = &data.post_ids { + post_ids.extend(post_ids_.iter().cloned()); + } + + if let Some(post_id) = data.post_id { + post_ids.insert(post_id); + } if post_ids.len() > MAX_API_PARAM_ELEMENTS { Err(LemmyErrorType::TooManyItems)?; } + let person_id = local_user_view.person.id; + // Mark the post as read / unread if data.read { PostRead::mark_as_read(&mut context.pool(), post_ids, person_id) diff --git a/crates/api_common/src/post.rs b/crates/api_common/src/post.rs index fbb6f1d38..b93742bea 100644 --- a/crates/api_common/src/post.rs +++ b/crates/api_common/src/post.rs @@ -135,14 +135,15 @@ pub struct RemovePost { pub reason: Option, } +#[skip_serializing_none] #[derive(Debug, Serialize, Deserialize, Clone, Default)] #[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", ts(export))] /// Mark a post as read. pub struct MarkPostAsRead { /// TODO: deprecated, send `post_ids` instead - pub post_id: PostId, - pub post_ids: Vec, + pub post_id: Option, + pub post_ids: Option>, pub read: bool, } From 6bcb12b14f056b8994feb39d8c30251ee26196ca Mon Sep 17 00:00:00 2001 From: Dessalines Date: Thu, 19 Oct 2023 20:34:48 -0400 Subject: [PATCH 24/24] Version 0.19.0-rc.3 --- Cargo.lock | 24 ++++++++++++------------ Cargo.toml | 24 ++++++++++++------------ crates/utils/translations | 2 +- 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 073e0a95c..de86ce622 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2621,7 +2621,7 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "lemmy_api" -version = "0.19.0-rc.2" +version = "0.19.0-rc.3" dependencies = [ "activitypub_federation", "actix-web", @@ -2652,7 +2652,7 @@ dependencies = [ [[package]] name = "lemmy_api_common" -version = "0.19.0-rc.2" +version = "0.19.0-rc.3" dependencies = [ "activitypub_federation", "actix-web", @@ -2687,7 +2687,7 @@ dependencies = [ [[package]] name = "lemmy_api_crud" -version = "0.19.0-rc.2" +version = "0.19.0-rc.3" dependencies = [ "activitypub_federation", "actix-web", @@ -2708,7 +2708,7 @@ dependencies = [ [[package]] name = "lemmy_apub" -version = "0.19.0-rc.2" +version = "0.19.0-rc.3" dependencies = [ "activitypub_federation", "actix-web", @@ -2747,7 +2747,7 @@ dependencies = [ [[package]] name = "lemmy_db_schema" -version = "0.19.0-rc.2" +version = "0.19.0-rc.3" dependencies = [ "activitypub_federation", "async-trait", @@ -2783,7 +2783,7 @@ dependencies = [ [[package]] name = "lemmy_db_views" -version = "0.19.0-rc.2" +version = "0.19.0-rc.3" dependencies = [ "actix-web", "diesel", @@ -2801,7 +2801,7 @@ dependencies = [ [[package]] name = "lemmy_db_views_actor" -version = "0.19.0-rc.2" +version = "0.19.0-rc.3" dependencies = [ "chrono", "diesel", @@ -2816,7 +2816,7 @@ dependencies = [ [[package]] name = "lemmy_db_views_moderator" -version = "0.19.0-rc.2" +version = "0.19.0-rc.3" dependencies = [ "diesel", "diesel-async", @@ -2828,7 +2828,7 @@ dependencies = [ [[package]] name = "lemmy_federate" -version = "0.19.0-rc.2" +version = "0.19.0-rc.3" dependencies = [ "activitypub_federation", "anyhow", @@ -2860,7 +2860,7 @@ dependencies = [ [[package]] name = "lemmy_routes" -version = "0.19.0-rc.2" +version = "0.19.0-rc.3" dependencies = [ "activitypub_federation", "actix-web", @@ -2885,7 +2885,7 @@ dependencies = [ [[package]] name = "lemmy_server" -version = "0.19.0-rc.2" +version = "0.19.0-rc.3" dependencies = [ "activitypub_federation", "actix-cors", @@ -2933,7 +2933,7 @@ dependencies = [ [[package]] name = "lemmy_utils" -version = "0.19.0-rc.2" +version = "0.19.0-rc.3" dependencies = [ "actix-web", "anyhow", diff --git a/Cargo.toml b/Cargo.toml index 9bf1000b6..a01cc687b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace.package] -version = "0.19.0-rc.2" +version = "0.19.0-rc.3" edition = "2021" description = "A link aggregator for the fediverse" license = "AGPL-3.0" @@ -60,16 +60,16 @@ members = [ ] [workspace.dependencies] -lemmy_api = { version = "=0.19.0-rc.2", path = "./crates/api" } -lemmy_api_crud = { version = "=0.19.0-rc.2", path = "./crates/api_crud" } -lemmy_apub = { version = "=0.19.0-rc.2", path = "./crates/apub" } -lemmy_utils = { version = "=0.19.0-rc.2", path = "./crates/utils" } -lemmy_db_schema = { version = "=0.19.0-rc.2", path = "./crates/db_schema" } -lemmy_api_common = { version = "=0.19.0-rc.2", path = "./crates/api_common" } -lemmy_routes = { version = "=0.19.0-rc.2", path = "./crates/routes" } -lemmy_db_views = { version = "=0.19.0-rc.2", path = "./crates/db_views" } -lemmy_db_views_actor = { version = "=0.19.0-rc.2", path = "./crates/db_views_actor" } -lemmy_db_views_moderator = { version = "=0.19.0-rc.2", path = "./crates/db_views_moderator" } +lemmy_api = { version = "=0.19.0-rc.3", path = "./crates/api" } +lemmy_api_crud = { version = "=0.19.0-rc.3", path = "./crates/api_crud" } +lemmy_apub = { version = "=0.19.0-rc.3", path = "./crates/apub" } +lemmy_utils = { version = "=0.19.0-rc.3", path = "./crates/utils" } +lemmy_db_schema = { version = "=0.19.0-rc.3", path = "./crates/db_schema" } +lemmy_api_common = { version = "=0.19.0-rc.3", path = "./crates/api_common" } +lemmy_routes = { version = "=0.19.0-rc.3", path = "./crates/routes" } +lemmy_db_views = { version = "=0.19.0-rc.3", path = "./crates/db_views" } +lemmy_db_views_actor = { version = "=0.19.0-rc.3", path = "./crates/db_views_actor" } +lemmy_db_views_moderator = { version = "=0.19.0-rc.3", path = "./crates/db_views_moderator" } activitypub_federation = { version = "0.5.0-beta.3", default-features = false, features = [ "actix-web", ] } @@ -139,7 +139,7 @@ lemmy_utils = { workspace = true } lemmy_db_schema = { workspace = true } lemmy_api_common = { workspace = true } lemmy_routes = { workspace = true } -lemmy_federate = { version = "0.19.0-rc.2", path = "crates/federate" } +lemmy_federate = { version = "0.19.0-rc.3", path = "crates/federate" } activitypub_federation = { workspace = true } diesel = { workspace = true } diesel-async = { workspace = true } diff --git a/crates/utils/translations b/crates/utils/translations index d0f354837..abd40d473 160000 --- a/crates/utils/translations +++ b/crates/utils/translations @@ -1 +1 @@ -Subproject commit d0f3548379e446d2c333e582734bc68f8d684f4d +Subproject commit abd40d4737fa732321fd7b62e42bbfcd51081cb6