From dfc74835b1dc6d5bc85ef8fa06e734a9c115bbe1 Mon Sep 17 00:00:00 2001 From: Nutomic Date: Fri, 20 Oct 2023 16:01:29 +0200 Subject: [PATCH 01/11] Hide deleted user accounts, add tests for PersonView (fixes #3811) (#4070) * Hide deleted user accounts, add tests for PersonView (fixes #3811) * clippy --- Cargo.lock | 2 + crates/db_views_actor/Cargo.toml | 4 + crates/db_views_actor/src/person_view.rs | 163 +++++++++++++++++++++++ 3 files changed, 169 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index de86ce6224..378f6cb2ec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2809,8 +2809,10 @@ dependencies = [ "lemmy_db_schema", "serde", "serde_with", + "serial_test", "strum", "strum_macros", + "tokio", "ts-rs", ] diff --git a/crates/db_views_actor/Cargo.toml b/crates/db_views_actor/Cargo.toml index 358bf0cabb..93ce0f5b39 100644 --- a/crates/db_views_actor/Cargo.toml +++ b/crates/db_views_actor/Cargo.toml @@ -31,3 +31,7 @@ ts-rs = { workspace = true, optional = true } chrono.workspace = true strum = { workspace = true } strum_macros = { workspace = true } + +[dev-dependencies] +serial_test = { workspace = true } +tokio = { workspace = true } diff --git a/crates/db_views_actor/src/person_view.rs b/crates/db_views_actor/src/person_view.rs index d06654f986..9e7c4d7e67 100644 --- a/crates/db_views_actor/src/person_view.rs +++ b/crates/db_views_actor/src/person_view.rs @@ -52,6 +52,7 @@ fn queries<'a>( query .inner_join(person_aggregates::table) .left_join(local_user::table) + .filter(person::deleted.eq(false)) .select((person::all_columns, person_aggregates::all_columns)) }; @@ -151,3 +152,165 @@ impl PersonQuery { queries().list(pool, ListMode::Query(self)).await } } + +#[cfg(test)] +mod tests { + #![allow(clippy::unwrap_used)] + #![allow(clippy::indexing_slicing)] + + use super::*; + use diesel::NotFound; + use lemmy_db_schema::{ + source::{ + instance::Instance, + local_user::{LocalUser, LocalUserInsertForm, LocalUserUpdateForm}, + person::{Person, PersonInsertForm, PersonUpdateForm}, + }, + traits::Crud, + utils::build_db_pool_for_tests, + }; + use serial_test::serial; + + struct Data { + alice: Person, + alice_local_user: LocalUser, + bob: Person, + bob_local_user: LocalUser, + } + + async fn init_data(pool: &mut DbPool<'_>) -> Data { + let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()) + .await + .unwrap(); + + let alice_form = PersonInsertForm::builder() + .name("alice".to_string()) + .public_key("pubkey".to_string()) + .instance_id(inserted_instance.id) + .build(); + let alice = Person::create(pool, &alice_form).await.unwrap(); + let alice_local_user_form = LocalUserInsertForm::builder() + .person_id(alice.id) + .password_encrypted(String::new()) + .build(); + let alice_local_user = LocalUser::create(pool, &alice_local_user_form) + .await + .unwrap(); + + let bob_form = PersonInsertForm::builder() + .name("bob".to_string()) + .bot_account(Some(true)) + .public_key("pubkey".to_string()) + .instance_id(inserted_instance.id) + .build(); + let bob = Person::create(pool, &bob_form).await.unwrap(); + let bob_local_user_form = LocalUserInsertForm::builder() + .person_id(bob.id) + .password_encrypted(String::new()) + .build(); + let bob_local_user = LocalUser::create(pool, &bob_local_user_form).await.unwrap(); + + Data { + alice, + alice_local_user, + bob, + bob_local_user, + } + } + + async fn cleanup(data: Data, pool: &mut DbPool<'_>) { + LocalUser::delete(pool, data.alice_local_user.id) + .await + .unwrap(); + LocalUser::delete(pool, data.bob_local_user.id) + .await + .unwrap(); + Person::delete(pool, data.alice.id).await.unwrap(); + Person::delete(pool, data.bob.id).await.unwrap(); + Instance::delete(pool, data.bob.instance_id).await.unwrap(); + } + + #[tokio::test] + #[serial] + async fn exclude_deleted() { + let pool = &build_db_pool_for_tests().await; + let pool = &mut pool.into(); + let data = init_data(pool).await; + + Person::update( + pool, + data.alice.id, + &PersonUpdateForm { + deleted: Some(true), + ..Default::default() + }, + ) + .await + .unwrap(); + + let read = PersonView::read(pool, data.alice.id).await; + assert_eq!(read.err(), Some(NotFound)); + + let list = PersonQuery::default().list(pool).await.unwrap(); + assert_eq!(list.len(), 1); + assert_eq!(list[0].person.id, data.bob.id); + + cleanup(data, pool).await; + } + + #[tokio::test] + #[serial] + async fn list_banned() { + let pool = &build_db_pool_for_tests().await; + let pool = &mut pool.into(); + let data = init_data(pool).await; + + Person::update( + pool, + data.alice.id, + &PersonUpdateForm { + banned: Some(true), + ..Default::default() + }, + ) + .await + .unwrap(); + + let list = PersonView::banned(pool).await.unwrap(); + assert_eq!(list.len(), 1); + assert_eq!(list[0].person.id, data.alice.id); + + cleanup(data, pool).await; + } + + #[tokio::test] + #[serial] + async fn list_admins() { + let pool = &build_db_pool_for_tests().await; + let pool = &mut pool.into(); + let data = init_data(pool).await; + + LocalUser::update( + pool, + data.alice_local_user.id, + &LocalUserUpdateForm { + admin: Some(true), + ..Default::default() + }, + ) + .await + .unwrap(); + + let list = PersonView::admins(pool).await.unwrap(); + assert_eq!(list.len(), 1); + assert_eq!(list[0].person.id, data.alice.id); + + let is_admin = PersonView::is_admin(pool, data.alice.id).await.unwrap(); + assert!(is_admin); + + let is_admin = PersonView::is_admin(pool, data.bob.id).await.unwrap(); + assert!(!is_admin); + + cleanup(data, pool).await; + } +} From ec0a707110dc4f8641bdfeda8595159a81272259 Mon Sep 17 00:00:00 2001 From: Nutomic Date: Fri, 20 Oct 2023 17:09:34 +0200 Subject: [PATCH 02/11] Avoid using proxy for pictrs requests (fixes #3489) (#4072) * Avoid using proxy for pictrs requests (fixes #3489) * fmt --- crates/api_common/src/request.rs | 25 ++++++++---------- crates/apub/src/objects/mod.rs | 10 +++---- docker/docker-compose.yml | 2 +- docker/docker_update.sh | 4 +-- src/lib.rs | 45 ++++++++++++-------------------- 5 files changed, 33 insertions(+), 53 deletions(-) diff --git a/crates/api_common/src/request.rs b/crates/api_common/src/request.rs index b27bf2e927..0064c80455 100644 --- a/crates/api_common/src/request.rs +++ b/crates/api_common/src/request.rs @@ -8,6 +8,7 @@ use lemmy_utils::{ REQWEST_TIMEOUT, }; use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC}; +use reqwest::{Client, ClientBuilder}; use reqwest_middleware::ClientWithMiddleware; use serde::Deserialize; use tracing::info; @@ -288,12 +289,17 @@ async fn is_image_content_type(client: &ClientWithMiddleware, url: &Url) -> Resu } } -pub fn build_user_agent(settings: &Settings) -> String { - format!( +pub fn client_builder(settings: &Settings) -> ClientBuilder { + let user_agent = format!( "Lemmy/{}; +{}", VERSION, settings.get_protocol_and_hostname() - ) + ); + + Client::builder() + .user_agent(user_agent.clone()) + .timeout(REQWEST_TIMEOUT) + .connect_timeout(REQWEST_TIMEOUT) } #[cfg(test)] @@ -301,12 +307,7 @@ mod tests { #![allow(clippy::unwrap_used)] #![allow(clippy::indexing_slicing)] - use crate::request::{ - build_user_agent, - fetch_site_metadata, - html_to_site_metadata, - SiteMetadata, - }; + use crate::request::{client_builder, fetch_site_metadata, html_to_site_metadata, SiteMetadata}; use lemmy_utils::settings::SETTINGS; use url::Url; @@ -314,11 +315,7 @@ mod tests { #[tokio::test] async fn test_site_metadata() { let settings = &SETTINGS.clone(); - let client = reqwest::Client::builder() - .user_agent(build_user_agent(settings)) - .build() - .unwrap() - .into(); + let client = client_builder(settings).build().unwrap().into(); let sample_url = Url::parse("https://gitlab.com/IzzyOnDroid/repo/-/wikis/FAQ").unwrap(); let sample_res = fetch_site_metadata(&client, &sample_url).await.unwrap(); assert_eq!( diff --git a/crates/apub/src/objects/mod.rs b/crates/apub/src/objects/mod.rs index 6e27c0d093..116c7f4fb7 100644 --- a/crates/apub/src/objects/mod.rs +++ b/crates/apub/src/objects/mod.rs @@ -59,10 +59,10 @@ pub(crate) mod tests { use activitypub_federation::config::{Data, FederationConfig}; use anyhow::anyhow; - use lemmy_api_common::{context::LemmyContext, request::build_user_agent}; + use lemmy_api_common::{context::LemmyContext, request::client_builder}; use lemmy_db_schema::{source::secret::Secret, utils::build_db_pool_for_tests}; use lemmy_utils::{rate_limit::RateLimitCell, settings::SETTINGS}; - use reqwest::{Client, Request, Response}; + use reqwest::{Request, Response}; use reqwest_middleware::{ClientBuilder, Middleware, Next}; use task_local_extensions::Extensions; @@ -86,11 +86,7 @@ pub(crate) mod tests { // call this to run migrations let pool = build_db_pool_for_tests().await; - let settings = SETTINGS.clone(); - let client = Client::builder() - .user_agent(build_user_agent(&settings)) - .build() - .unwrap(); + let client = client_builder(&SETTINGS).build().unwrap(); let client = ClientBuilder::new(client).with(BlockedMiddleware).build(); let secret = Secret { diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index a61f259730..bd339aabb5 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -55,7 +55,7 @@ services: lemmy-ui: # use "image" to pull down an already compiled lemmy-ui. make sure to comment out "build". - image: dessalines/lemmy-ui:0.18.4 + image: dessalines/lemmy-ui:0.19.0-rc.3 # platform: linux/x86_64 # no arm64 support. uncomment platform if using m1. # use "build" to build your local lemmy ui image for development. make sure to comment out "image". # run: docker compose up --build diff --git a/docker/docker_update.sh b/docker/docker_update.sh index d64025cc1b..45411f1a3d 100755 --- a/docker/docker_update.sh +++ b/docker/docker_update.sh @@ -45,9 +45,9 @@ if [ "$ARCH" = 'arm64' ]; then fi echo "$LOG_PREFIX Initializing images in the background. Please be patient if compiling from source..." - docker compose up -d --build + docker compose up --build else - sudo docker compose up -d --build + sudo docker compose up --build fi echo "$LOG_PREFIX Complete! You can now access the UI at http://localhost:1236." diff --git a/src/lib.rs b/src/lib.rs index 2df231dd5a..c12281cdfd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,7 +28,7 @@ use clap::{ArgAction, Parser}; use lemmy_api_common::{ context::LemmyContext, lemmy_db_views::structs::SiteView, - request::build_user_agent, + request::client_builder, send_activity::{ActivityChannel, MATCH_OUTGOING_ACTIVITIES}, utils::{ check_private_instance_and_federation_enabled, @@ -52,11 +52,10 @@ use lemmy_utils::{ response::jsonify_plain_text_errors, settings::{structs::Settings, SETTINGS}, }; -use reqwest::Client; use reqwest_middleware::{ClientBuilder, ClientWithMiddleware}; use reqwest_tracing::TracingMiddleware; use serde_json::json; -use std::{env, ops::Deref, time::Duration}; +use std::{env, ops::Deref}; use tokio::signal::unix::SignalKind; use tracing::subscriber::set_global_default; use tracing_actix_web::TracingLogger; @@ -112,13 +111,9 @@ pub struct CmdArgs { #[arg(long, default_value_t = 1)] federate_process_count: i32, } -/// Max timeout for http requests -pub(crate) const REQWEST_TIMEOUT: Duration = Duration::from_secs(10); /// Placing the main function in lib.rs allows other crates to import it and embed Lemmy pub async fn start_lemmy_server(args: CmdArgs) -> Result<(), LemmyError> { - let settings = SETTINGS.to_owned(); - // return error 503 while running db migrations and startup tasks let mut startup_server_handle = None; if args.http_server { @@ -126,14 +121,14 @@ pub async fn start_lemmy_server(args: CmdArgs) -> Result<(), LemmyError> { } // Run the DB migrations - let db_url = get_database_url(Some(&settings)); + let db_url = get_database_url(Some(&SETTINGS)); run_migrations(&db_url); // Set up the connection pool - let pool = build_db_pool(&settings).await?; + let pool = build_db_pool(&SETTINGS).await?; // Run the Code-required migrations - run_advanced_migrations(&mut (&pool).into(), &settings).await?; + run_advanced_migrations(&mut (&pool).into(), &SETTINGS).await?; // Initialize the secrets let secret = Secret::init(&mut (&pool).into()) @@ -148,7 +143,7 @@ pub async fn start_lemmy_server(args: CmdArgs) -> Result<(), LemmyError> { let federation_enabled = local_site.federation_enabled; if federation_enabled { - println!("federation enabled, host is {}", &settings.hostname); + println!("federation enabled, host is {}", &SETTINGS.hostname); } check_private_instance_and_federation_enabled(&local_site)?; @@ -160,25 +155,12 @@ pub async fn start_lemmy_server(args: CmdArgs) -> Result<(), LemmyError> { println!( "Starting http server at {}:{}", - settings.bind, settings.port + SETTINGS.bind, SETTINGS.port ); - let user_agent = build_user_agent(&settings); - let reqwest_client = Client::builder() - .user_agent(user_agent.clone()) - .timeout(REQWEST_TIMEOUT) - .connect_timeout(REQWEST_TIMEOUT) - .build()?; - - let client = ClientBuilder::new(reqwest_client.clone()) + let client = ClientBuilder::new(client_builder(&SETTINGS).build()?) .with(TracingMiddleware::default()) .build(); - - // Pictrs cannot use the retry middleware - let pictrs_client = ClientBuilder::new(reqwest_client.clone()) - .with(TracingMiddleware::default()) - .build(); - let context = LemmyContext::create( pool.clone(), client.clone(), @@ -192,10 +174,10 @@ pub async fn start_lemmy_server(args: CmdArgs) -> Result<(), LemmyError> { } #[cfg(feature = "prometheus-metrics")] - serve_prometheus(settings.prometheus.as_ref(), context.clone()); + serve_prometheus(SETTINGS.prometheus.as_ref(), context.clone()); let federation_config = FederationConfig::builder() - .domain(settings.hostname.clone()) + .domain(SETTINGS.hostname.clone()) .app_data(context.clone()) .client(client.clone()) .http_fetch_limit(FEDERATION_HTTP_FETCH_LIMIT) @@ -217,9 +199,14 @@ pub async fn start_lemmy_server(args: CmdArgs) -> Result<(), LemmyError> { if let Some(startup_server_handle) = startup_server_handle { startup_server_handle.stop(true).await; } + + // Pictrs cannot use proxy + let pictrs_client = ClientBuilder::new(client_builder(&SETTINGS).no_proxy().build()?) + .with(TracingMiddleware::default()) + .build(); Some(create_http_server( federation_config.clone(), - settings.clone(), + SETTINGS.clone(), federation_enabled, pictrs_client, )?) From 6235ff45b583949b413aa6986a90fdc073bcf9d9 Mon Sep 17 00:00:00 2001 From: Nutomic Date: Mon, 23 Oct 2023 18:17:26 +0200 Subject: [PATCH 03/11] Return status 401 on logout with invalid auth (fixes #4081) (#4082) * Return status 401 on logout with invalid auth (fixes #4081) * format --- crates/utils/src/error.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/utils/src/error.rs b/crates/utils/src/error.rs index 714fdfe56c..9fa6bc508b 100644 --- a/crates/utils/src/error.rs +++ b/crates/utils/src/error.rs @@ -56,6 +56,9 @@ impl Display for LemmyError { impl actix_web::error::ResponseError for LemmyError { fn status_code(&self) -> http::StatusCode { + if self.error_type == LemmyErrorType::IncorrectLogin { + return http::StatusCode::UNAUTHORIZED; + } match self.inner.downcast_ref::() { Some(diesel::result::Error::NotFound) => http::StatusCode::NOT_FOUND, _ => http::StatusCode::BAD_REQUEST, From 5bfa4e9358cb10dc9e93d0dbdfca57b501a9a529 Mon Sep 17 00:00:00 2001 From: Dessalines Date: Mon, 23 Oct 2023 12:40:29 -0400 Subject: [PATCH 04/11] Make sure my_vote is consistently missing if not voted. Fixes #3197 (#4075) * Make sure my_vote is consistently missing if not voted. Fixes #3197 * Fix clippy. * Fix tests. --- crates/api_common/src/post.rs | 1 + crates/db_views/src/post_view.rs | 10 ++-------- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/crates/api_common/src/post.rs b/crates/api_common/src/post.rs index b93742bea5..c7ee089833 100644 --- a/crates/api_common/src/post.rs +++ b/crates/api_common/src/post.rs @@ -47,6 +47,7 @@ pub struct GetPost { pub comment_id: Option, } +#[skip_serializing_none] #[derive(Debug, Serialize, Deserialize, Clone)] #[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", ts(export))] diff --git a/crates/db_views/src/post_view.rs b/crates/db_views/src/post_view.rs index 82e830ac19..c3da47ac94 100644 --- a/crates/db_views/src/post_view.rs +++ b/crates/db_views/src/post_view.rs @@ -542,16 +542,10 @@ impl PostView { my_person_id: Option, is_mod_or_admin: bool, ) -> Result { - let mut res = queries() + let res = queries() .read(pool, (post_id, my_person_id, is_mod_or_admin)) .await?; - // If a person is given, then my_vote, if None, should be 0, not null - // Necessary to differentiate between other person's votes - if my_person_id.is_some() && res.my_vote.is_none() { - res.my_vote = Some(0) - }; - Ok(res) } } @@ -877,7 +871,7 @@ mod tests { assert_eq!(1, read_post_listing.len()); assert_eq!(expected_post_listing_with_user, read_post_listing[0]); - expected_post_listing_with_user.my_vote = Some(0); + expected_post_listing_with_user.my_vote = None; assert_eq!( expected_post_listing_with_user, post_listing_single_with_person From 1596aee724339c7112d5efa42fa37e838e87d93c Mon Sep 17 00:00:00 2001 From: Dessalines Date: Tue, 24 Oct 2023 04:49:52 -0400 Subject: [PATCH 05/11] Adding /version route. Fixes #2914 (#4059) * Adding /version route. Fixes #2914 * Using a simple version string. * Use nginx rewriting to solve version. * Forgot to remove version. * Using an actix redirect. --- crates/routes/src/nodeinfo.rs | 1 + docker/nginx.conf | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/routes/src/nodeinfo.rs b/crates/routes/src/nodeinfo.rs index 736ab4e046..06d4c2dd68 100644 --- a/crates/routes/src/nodeinfo.rs +++ b/crates/routes/src/nodeinfo.rs @@ -17,6 +17,7 @@ pub fn config(cfg: &mut web::ServiceConfig) { "/nodeinfo/2.0.json", web::get().to(node_info).wrap(cache_1hour()), ) + .service(web::redirect("/version", "/nodeinfo/2.0.json")) .route( "/.well-known/nodeinfo", web::get().to(node_info_well_known).wrap(cache_3days()), diff --git a/docker/nginx.conf b/docker/nginx.conf index fd52ec84e2..529e64a2a0 100644 --- a/docker/nginx.conf +++ b/docker/nginx.conf @@ -56,7 +56,7 @@ http { } # backend - location ~ ^/(api|pictrs|feeds|nodeinfo|.well-known) { + location ~ ^/(api|pictrs|feeds|nodeinfo|version|.well-known) { proxy_pass "http://lemmy"; # proxy common stuff proxy_http_version 1.1; From 5540257b36df3bcad7433c6c0a299cababd8cfb8 Mon Sep 17 00:00:00 2001 From: Dessalines Date: Tue, 24 Oct 2023 05:20:23 -0400 Subject: [PATCH 06/11] =?UTF-8?q?Fixing=20problem=20with=20SaveUserSetting?= =?UTF-8?q?s,=20when=20either=20the=20Person=20or=20Local=E2=80=A6=20(#407?= =?UTF-8?q?7)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fixing problem with SaveUserSettings, when either the Person or LocalUser update is null. - Fixes #4076 - Also upgrading api_test deps * Move function into test. --- api_tests/package.json | 10 +- api_tests/src/community.spec.ts | 1 - api_tests/src/user.spec.ts | 12 +- api_tests/yarn.lock | 160 +++++++++++---------- crates/api/src/local_user/save_settings.rs | 12 +- 5 files changed, 108 insertions(+), 87 deletions(-) diff --git a/api_tests/package.json b/api_tests/package.json index 84e5d3df57..92e00b81bc 100644 --- a/api_tests/package.json +++ b/api_tests/package.json @@ -12,11 +12,11 @@ "api-test": "jest -i follow.spec.ts && jest -i post.spec.ts && jest -i comment.spec.ts && jest -i private_message.spec.ts && jest -i user.spec.ts && jest -i community.spec.ts" }, "devDependencies": { - "@types/jest": "^29.5.1", - "@types/node": "^20.8.6", - "@typescript-eslint/eslint-plugin": "^6.7.5", - "@typescript-eslint/parser": "^6.7.5", - "eslint": "^8.51.0", + "@types/jest": "^29.5.6", + "@types/node": "^20.8.7", + "@typescript-eslint/eslint-plugin": "^6.8.0", + "@typescript-eslint/parser": "^6.8.0", + "eslint": "^8.52.0", "eslint-plugin-prettier": "^5.0.1", "jest": "^29.5.0", "lemmy-js-client": "0.19.0-rc.12", diff --git a/api_tests/src/community.spec.ts b/api_tests/src/community.spec.ts index 2c97d629f5..2f3a410f61 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, diff --git a/api_tests/src/user.spec.ts b/api_tests/src/user.spec.ts index db36494d75..eddf568b84 100644 --- a/api_tests/src/user.spec.ts +++ b/api_tests/src/user.spec.ts @@ -17,8 +17,9 @@ import { saveUserSettingsFederated, setupLogins, alphaUrl, + saveUserSettings, } from "./shared"; -import { LemmyHttp } from "lemmy-js-client"; +import { LemmyHttp, SaveUserSettings } from "lemmy-js-client"; import { GetPosts } from "lemmy-js-client/dist/types/GetPosts"; beforeAll(async () => { @@ -57,6 +58,15 @@ test("Set some user settings, check that they are federated", async () => { let alphaPerson = (await resolvePerson(alpha, apShortname)).person; let betaPerson = (await resolvePerson(beta, apShortname)).person; assertUserFederation(alphaPerson, betaPerson); + + // Catches a bug where when only the person or local_user changed + let form: SaveUserSettings = { + theme: "test", + }; + await saveUserSettings(beta, form); + + let site = await getSite(beta); + expect(site.my_user?.local_user_view.local_user.theme).toBe("test"); }); test("Delete user", async () => { diff --git a/api_tests/yarn.lock b/api_tests/yarn.lock index 56764e0969..ca73cf9fc8 100644 --- a/api_tests/yarn.lock +++ b/api_tests/yarn.lock @@ -329,17 +329,17 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@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== +"@eslint/js@8.52.0": + version "8.52.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.52.0.tgz#78fe5f117840f69dc4a353adf9b9cd926353378c" + integrity sha512-mjZVbpaeMZludF2fsWLD0Z9gCref1Tk4i9+wddjRvpUNqqcndPkBD09N/Mapey0b3jaXbLm2kICwFv2E64QinA== -"@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== +"@humanwhocodes/config-array@^0.11.13": + version "0.11.13" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.13.tgz#075dc9684f40a531d9b26b0822153c1e832ee297" + integrity sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ== dependencies: - "@humanwhocodes/object-schema" "^1.2.1" + "@humanwhocodes/object-schema" "^2.0.1" debug "^4.1.1" minimatch "^3.0.5" @@ -348,10 +348,10 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== -"@humanwhocodes/object-schema@^1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" - integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== +"@humanwhocodes/object-schema@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz#e5211452df060fa8522b55c7b3c0c4d1981cb044" + integrity sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw== "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" @@ -704,10 +704,10 @@ dependencies: "@types/istanbul-lib-report" "*" -"@types/jest@^29.5.1": - version "29.5.5" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.5.tgz#727204e06228fe24373df9bae76b90f3e8236a2a" - integrity sha512-ebylz2hnsWR9mYvmBFbXJXr+33UPc4+ZdxyDXh5w0FlPBTfCVN3wPL+kuOiQt3xvrK419v7XWeAs+AeOksafXg== +"@types/jest@^29.5.6": + version "29.5.6" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.6.tgz#f4cf7ef1b5b0bfc1aa744e41b24d9cc52533130b" + integrity sha512-/t9NnzkOpXb4Nfvg17ieHE6EeSjDS2SGSpNYfoLbUAeL/EOueU/RSdOWFpfQTXBEM7BguYW1XQ0EbM+6RlIh6w== dependencies: expect "^29.0.0" pretty-format "^29.0.0" @@ -722,10 +722,10 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-20.8.0.tgz#10ddf0119cf20028781c06d7115562934e53f745" integrity sha512-LzcWltT83s1bthcvjBmiBvGJiiUe84NWRHkw+ZV6Fr41z2FbIzvc815dk2nQ3RAKMuN2fkenM/z3Xv2QzEpYxQ== -"@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== +"@types/node@^20.8.7": + version "20.8.7" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.8.7.tgz#ad23827850843de973096edfc5abc9e922492a25" + integrity sha512-21TKHHh3eUHIi2MloeptJWALuCu5H7HQTdTrWIFReA8ad+aggoX+lRes3ex7/FtpC+sVUpFMQ+QTfYr74mruiQ== dependencies: undici-types "~5.25.1" @@ -751,16 +751,16 @@ dependencies: "@types/yargs-parser" "*" -"@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== +"@typescript-eslint/eslint-plugin@^6.8.0": + version "6.8.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.8.0.tgz#06abe4265e7c82f20ade2dcc0e3403c32d4f148b" + integrity sha512-GosF4238Tkes2SHPQ1i8f6rMtG6zlKwMEB0abqSJ3Npvos+doIlc/ATG+vX1G9coDF3Ex78zM3heXHLyWEwLUw== dependencies: "@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" + "@typescript-eslint/scope-manager" "6.8.0" + "@typescript-eslint/type-utils" "6.8.0" + "@typescript-eslint/utils" "6.8.0" + "@typescript-eslint/visitor-keys" "6.8.0" debug "^4.3.4" graphemer "^1.4.0" ignore "^5.2.4" @@ -768,74 +768,79 @@ semver "^7.5.4" ts-api-utils "^1.0.1" -"@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== +"@typescript-eslint/parser@^6.8.0": + version "6.8.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.8.0.tgz#bb2a969d583db242f1ee64467542f8b05c2e28cb" + integrity sha512-5tNs6Bw0j6BdWuP8Fx+VH4G9fEPDxnVI7yH1IAPkQH5RUtvKwRoqdecAPdQXv4rSOADAaz1LFBZvZG7VbXivSg== dependencies: - "@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" + "@typescript-eslint/scope-manager" "6.8.0" + "@typescript-eslint/types" "6.8.0" + "@typescript-eslint/typescript-estree" "6.8.0" + "@typescript-eslint/visitor-keys" "6.8.0" debug "^4.3.4" -"@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== +"@typescript-eslint/scope-manager@6.8.0": + version "6.8.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.8.0.tgz#5cac7977385cde068ab30686889dd59879811efd" + integrity sha512-xe0HNBVwCph7rak+ZHcFD6A+q50SMsFwcmfdjs9Kz4qDh5hWhaPhFjRs/SODEhroBI5Ruyvyz9LfwUJ624O40g== dependencies: - "@typescript-eslint/types" "6.7.5" - "@typescript-eslint/visitor-keys" "6.7.5" + "@typescript-eslint/types" "6.8.0" + "@typescript-eslint/visitor-keys" "6.8.0" -"@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== +"@typescript-eslint/type-utils@6.8.0": + version "6.8.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-6.8.0.tgz#50365e44918ca0fd159844b5d6ea96789731e11f" + integrity sha512-RYOJdlkTJIXW7GSldUIHqc/Hkto8E+fZN96dMIFhuTJcQwdRoGN2rEWA8U6oXbLo0qufH7NPElUb+MceHtz54g== dependencies: - "@typescript-eslint/typescript-estree" "6.7.5" - "@typescript-eslint/utils" "6.7.5" + "@typescript-eslint/typescript-estree" "6.8.0" + "@typescript-eslint/utils" "6.8.0" debug "^4.3.4" ts-api-utils "^1.0.1" -"@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/types@6.8.0": + version "6.8.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.8.0.tgz#1ab5d4fe1d613e3f65f6684026ade6b94f7e3ded" + integrity sha512-p5qOxSum7W3k+llc7owEStXlGmSl8FcGvhYt8Vjy7FqEnmkCVlM3P57XQEGj58oqaBWDQXbJDZxwUWMS/EAPNQ== -"@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== +"@typescript-eslint/typescript-estree@6.8.0": + version "6.8.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.8.0.tgz#9565f15e0cd12f55cf5aa0dfb130a6cb0d436ba1" + integrity sha512-ISgV0lQ8XgW+mvv5My/+iTUdRmGspducmQcDw5JxznasXNnZn3SKNrTRuMsEXv+V/O+Lw9AGcQCfVaOPCAk/Zg== dependencies: - "@typescript-eslint/types" "6.7.5" - "@typescript-eslint/visitor-keys" "6.7.5" + "@typescript-eslint/types" "6.8.0" + "@typescript-eslint/visitor-keys" "6.8.0" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" semver "^7.5.4" ts-api-utils "^1.0.1" -"@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== +"@typescript-eslint/utils@6.8.0": + version "6.8.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.8.0.tgz#d42939c2074c6b59844d0982ce26a51d136c4029" + integrity sha512-dKs1itdE2qFG4jr0dlYLQVppqTE+Itt7GmIf/vX6CSvsW+3ov8PbWauVKyyfNngokhIO9sKZeRGCUo1+N7U98Q== dependencies: "@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" + "@typescript-eslint/scope-manager" "6.8.0" + "@typescript-eslint/types" "6.8.0" + "@typescript-eslint/typescript-estree" "6.8.0" semver "^7.5.4" -"@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== +"@typescript-eslint/visitor-keys@6.8.0": + version "6.8.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.8.0.tgz#cffebed56ae99c45eba901c378a6447b06be58b8" + integrity sha512-oqAnbA7c+pgOhW2OhGvxm0t1BULX5peQI/rLsNDpGM78EebV3C9IGbX5HNZabuZ6UQrYveCLjKo8Iy/lLlBkkg== dependencies: - "@typescript-eslint/types" "6.7.5" + "@typescript-eslint/types" "6.8.0" eslint-visitor-keys "^3.4.1" +"@ungap/structured-clone@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" + integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== + acorn-jsx@^5.3.2: version "5.3.2" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" @@ -1328,18 +1333,19 @@ 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.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== +eslint@^8.52.0: + version "8.52.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.52.0.tgz#d0cd4a1fac06427a61ef9242b9353f36ea7062fc" + integrity sha512-zh/JHnaixqHZsolRB/w9/02akBk9EPrOs9JwcTP2ek7yL5bVvXuRariiaAjjoJ5DvuwQ1WAE/HsMz+w17YgBCg== dependencies: "@eslint-community/eslint-utils" "^4.2.0" "@eslint-community/regexpp" "^4.6.1" "@eslint/eslintrc" "^2.1.2" - "@eslint/js" "8.51.0" - "@humanwhocodes/config-array" "^0.11.11" + "@eslint/js" "8.52.0" + "@humanwhocodes/config-array" "^0.11.13" "@humanwhocodes/module-importer" "^1.0.1" "@nodelib/fs.walk" "^1.2.8" + "@ungap/structured-clone" "^1.2.0" ajv "^6.12.4" chalk "^4.0.0" cross-spawn "^7.0.2" diff --git a/crates/api/src/local_user/save_settings.rs b/crates/api/src/local_user/save_settings.rs index 90f6b6dd8d..5236aee697 100644 --- a/crates/api/src/local_user/save_settings.rs +++ b/crates/api/src/local_user/save_settings.rs @@ -16,7 +16,7 @@ use lemmy_db_schema::{ }; use lemmy_db_views::structs::{LocalUserView, SiteView}; use lemmy_utils::{ - error::{LemmyError, LemmyErrorExt, LemmyErrorType}, + error::{LemmyError, LemmyErrorType}, utils::validation::{is_valid_bio_field, is_valid_display_name, is_valid_matrix_id}, }; @@ -90,9 +90,11 @@ pub async fn save_user_settings( ..Default::default() }; + // Ignore errors, because 'no fields updated' will return an error. + // https://github.com/LemmyNet/lemmy/issues/4076 Person::update(&mut context.pool(), person_id, &person_form) .await - .with_lemmy_type(LemmyErrorType::UserAlreadyExists)?; + .ok(); if let Some(discussion_languages) = data.discussion_languages.clone() { LocalUserLanguage::update(&mut context.pool(), discussion_languages, local_user_id).await?; @@ -120,7 +122,11 @@ pub async fn save_user_settings( ..Default::default() }; - LocalUser::update(&mut context.pool(), local_user_id, &local_user_form).await?; + // Ignore errors, because 'no fields updated' will return an error. + // https://github.com/LemmyNet/lemmy/issues/4076 + LocalUser::update(&mut context.pool(), local_user_id, &local_user_form) + .await + .ok(); Ok(Json(SuccessResponse::default())) } From 5d48ee3dc8942104af6c586c22e5b9faea68d10f Mon Sep 17 00:00:00 2001 From: Dessalines Date: Tue, 24 Oct 2023 08:37:03 -0400 Subject: [PATCH 07/11] Add creator_is_moderator to Comment and PostViews. Fixes #3347 (#4087) * Add creator_is_moderator to Comment and PostViews. Fixes #3347 * Fixing community_moderator join. * Addressing PR comments. --- crates/db_schema/src/lib.rs | 4 +-- crates/db_views/src/comment_view.rs | 45 +++++++++++++++++++++++++++-- crates/db_views/src/post_view.rs | 43 +++++++++++++++++++++++++-- crates/db_views/src/structs.rs | 2 ++ 4 files changed, 88 insertions(+), 6 deletions(-) diff --git a/crates/db_schema/src/lib.rs b/crates/db_schema/src/lib.rs index 5b908f356d..1aa0e4e882 100644 --- a/crates/db_schema/src/lib.rs +++ b/crates/db_schema/src/lib.rs @@ -30,8 +30,8 @@ pub mod newtypes; pub mod schema; #[cfg(feature = "full")] pub mod aliases { - use crate::schema::person; - diesel::alias!(person as person1: Person1, person as person2: Person2); + use crate::schema::{community_moderator, person}; + diesel::alias!(person as person1: Person1, person as person2: Person2, community_moderator as community_moderator1: CommunityModerator1); } pub mod source; #[cfg(feature = "full")] diff --git a/crates/db_views/src/comment_view.rs b/crates/db_views/src/comment_view.rs index 98536fb8f8..99f046cdf7 100644 --- a/crates/db_views/src/comment_view.rs +++ b/crates/db_views/src/comment_view.rs @@ -12,6 +12,7 @@ use diesel::{ use diesel_async::RunQueryDsl; use diesel_ltree::{nlevel, subpath, Ltree, LtreeExtensions}; use lemmy_db_schema::{ + aliases, newtypes::{CommentId, CommunityId, LocalUserId, PersonId, PostId}, schema::{ comment, @@ -90,6 +91,17 @@ fn queries<'a>() -> Queries< .and(community_moderator::person_id.eq(person_id_join)), ), ) + .left_join( + aliases::community_moderator1.on( + community::id + .eq(aliases::community_moderator1.field(community_moderator::community_id)) + .and( + aliases::community_moderator1 + .field(community_moderator::person_id) + .eq(comment::creator_id), + ), + ), + ) }; let selection = ( @@ -99,6 +111,10 @@ fn queries<'a>() -> Queries< community::all_columns, comment_aggregates::all_columns, community_person_ban::id.nullable().is_not_null(), + aliases::community_moderator1 + .field(community_moderator::id) + .nullable() + .is_not_null(), CommunityFollower::select_subscribed_type(), comment_saved::id.nullable().is_not_null(), person_block::id.nullable().is_not_null(), @@ -338,7 +354,7 @@ mod tests { source::{ actor_language::LocalUserLanguage, comment::{Comment, CommentInsertForm, CommentLike, CommentLikeForm, CommentUpdateForm}, - community::{Community, CommunityInsertForm}, + community::{Community, CommunityInsertForm, CommunityModerator, CommunityModeratorForm}, instance::Instance, language::Language, local_user::{LocalUser, LocalUserInsertForm}, @@ -346,7 +362,7 @@ mod tests { person_block::{PersonBlock, PersonBlockForm}, post::{Post, PostInsertForm}, }, - traits::{Blockable, Crud, Likeable}, + traits::{Blockable, Crud, Joinable, Likeable}, utils::build_db_pool_for_tests, SubscribedType, }; @@ -779,6 +795,30 @@ mod tests { cleanup(data, pool).await; } + #[tokio::test] + #[serial] + async fn test_creator_is_moderator() { + let pool = &build_db_pool_for_tests().await; + let pool = &mut pool.into(); + let data = init_data(pool).await; + + // Make one of the inserted persons a moderator + let person_id = data.inserted_person_2.id; + let community_id = data.inserted_community.id; + let form = CommunityModeratorForm { + community_id, + person_id, + }; + CommunityModerator::join(pool, &form).await.unwrap(); + + // Make sure that they come back as a mod in the list + let comments = CommentQuery::default().list(pool).await.unwrap(); + + assert!(comments[1].creator_is_moderator); + + cleanup(data, pool).await; + } + async fn cleanup(data: Data, pool: &mut DbPool<'_>) { CommentLike::remove( pool, @@ -814,6 +854,7 @@ mod tests { .unwrap(); CommentView { creator_banned_from_community: false, + creator_is_moderator: false, my_vote: None, subscribed: SubscribedType::NotSubscribed, saved: false, diff --git a/crates/db_views/src/post_view.rs b/crates/db_views/src/post_view.rs index c3da47ac94..51213abb20 100644 --- a/crates/db_views/src/post_view.rs +++ b/crates/db_views/src/post_view.rs @@ -107,6 +107,13 @@ fn queries<'a>() -> Queries< .and(community_person_ban::person_id.eq(post_aggregates::creator_id)), ), ); + let creator_is_moderator = exists( + community_moderator::table.filter( + post_aggregates::community_id + .eq(community_moderator::community_id) + .and(community_moderator::person_id.eq(post_aggregates::creator_id)), + ), + ); let is_saved = |person_id| { exists( @@ -226,6 +233,7 @@ fn queries<'a>() -> Queries< person::all_columns, community::all_columns, is_creator_banned_from_community, + creator_is_moderator, post_aggregates::all_columns, subscribed_type_selection, is_saved_selection, @@ -705,7 +713,7 @@ mod tests { newtypes::LanguageId, source::{ actor_language::LocalUserLanguage, - community::{Community, CommunityInsertForm}, + community::{Community, CommunityInsertForm, CommunityModerator, CommunityModeratorForm}, community_block::{CommunityBlock, CommunityBlockForm}, instance::Instance, instance_block::{InstanceBlock, InstanceBlockForm}, @@ -715,7 +723,7 @@ mod tests { person_block::{PersonBlock, PersonBlockForm}, post::{Post, PostInsertForm, PostLike, PostLikeForm, PostUpdateForm}, }, - traits::{Blockable, Crud, Likeable}, + traits::{Blockable, Crud, Joinable, Likeable}, utils::{build_db_pool_for_tests, DbPool}, SortType, SubscribedType, @@ -1063,6 +1071,36 @@ mod tests { cleanup(data, pool).await; } + #[tokio::test] + #[serial] + async fn creator_is_moderator() { + let pool = &build_db_pool_for_tests().await; + let pool = &mut pool.into(); + let data = init_data(pool).await; + + // Make one of the inserted persons a moderator + let person_id = data.local_user_view.person.id; + let community_id = data.inserted_community.id; + let form = CommunityModeratorForm { + community_id, + person_id, + }; + CommunityModerator::join(pool, &form).await.unwrap(); + + let post_listing = PostQuery { + sort: (Some(SortType::New)), + community_id: (Some(data.inserted_community.id)), + local_user: (Some(&data.local_user_view)), + ..Default::default() + } + .list(pool) + .await + .unwrap(); + + assert!(post_listing[1].creator_is_moderator); + cleanup(data, pool).await; + } + #[tokio::test] #[serial] async fn post_listing_person_language() { @@ -1396,6 +1434,7 @@ mod tests { last_refreshed_at: inserted_person.last_refreshed_at, }, creator_banned_from_community: false, + creator_is_moderator: false, community: Community { id: inserted_community.id, name: inserted_community.name.clone(), diff --git a/crates/db_views/src/structs.rs b/crates/db_views/src/structs.rs index 10847b0538..2f65bb78fe 100644 --- a/crates/db_views/src/structs.rs +++ b/crates/db_views/src/structs.rs @@ -56,6 +56,7 @@ pub struct CommentView { pub community: Community, pub counts: CommentAggregates, pub creator_banned_from_community: bool, + pub creator_is_moderator: bool, pub subscribed: SubscribedType, pub saved: bool, pub creator_blocked: bool, @@ -107,6 +108,7 @@ pub struct PostView { pub creator: Person, pub community: Community, pub creator_banned_from_community: bool, + pub creator_is_moderator: bool, pub counts: PostAggregates, pub subscribed: SubscribedType, pub saved: bool, From 053dcf8c104d580679bbf53b267c7160f359683f Mon Sep 17 00:00:00 2001 From: Dessalines Date: Tue, 24 Oct 2023 10:47:02 -0400 Subject: [PATCH 08/11] Fixing matrix id regex. Fixes #3431 (#4091) --- crates/utils/src/utils/validation.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/crates/utils/src/utils/validation.rs b/crates/utils/src/utils/validation.rs index 4d8ed8543e..cf9b1f218f 100644 --- a/crates/utils/src/utils/validation.rs +++ b/crates/utils/src/utils/validation.rs @@ -8,8 +8,11 @@ static VALID_ACTOR_NAME_REGEX: Lazy = Lazy::new(|| Regex::new(r"^[a-zA-Z0-9_]{3,}$").expect("compile regex")); static VALID_POST_TITLE_REGEX: Lazy = Lazy::new(|| Regex::new(r".*\S{3,200}.*").expect("compile regex")); + +// From here: https://github.com/vector-im/element-android/blob/develop/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixPatterns.kt#L35 static VALID_MATRIX_ID_REGEX: Lazy = Lazy::new(|| { - Regex::new(r"^@[A-Za-z0-9._=-]+:[A-Za-z0-9.-]+\.[A-Za-z]{2,}$").expect("compile regex") + Regex::new(r"^@[A-Za-z0-9\\x21-\\x39\\x3B-\\x7F]+:[A-Za-z0-9.-]+(:[0-9]{2,5})?$") + .expect("compile regex") }); // taken from https://en.wikipedia.org/wiki/UTM_parameters static CLEAN_URL_PARAMS_REGEX: Lazy = Lazy::new(|| { @@ -336,9 +339,11 @@ mod tests { #[test] fn test_valid_matrix_id() { assert!(is_valid_matrix_id("@dess:matrix.org").is_ok()); + assert!(is_valid_matrix_id("@dess:matrix.org:443").is_ok()); assert!(is_valid_matrix_id("dess:matrix.org").is_err()); assert!(is_valid_matrix_id(" @dess:matrix.org").is_err()); assert!(is_valid_matrix_id("@dess:matrix.org t").is_err()); + assert!(is_valid_matrix_id("@dess:matrix.org t").is_err()); } #[test] From 09a67472e7fb7ad8af8f901188e6f7b5645e2495 Mon Sep 17 00:00:00 2001 From: Dessalines Date: Tue, 24 Oct 2023 10:48:00 -0400 Subject: [PATCH 09/11] Allow torrent magnet links as post urls. Fixes #3916 (#4092) --- crates/utils/src/utils/validation.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/crates/utils/src/utils/validation.rs b/crates/utils/src/utils/validation.rs index cf9b1f218f..33c78b654d 100644 --- a/crates/utils/src/utils/validation.rs +++ b/crates/utils/src/utils/validation.rs @@ -19,6 +19,7 @@ static CLEAN_URL_PARAMS_REGEX: Lazy = Lazy::new(|| { Regex::new(r"^utm_source|utm_medium|utm_campaign|utm_term|utm_content|gclid|gclsrc|dclid|fbclid$") .expect("compile regex") }); +const ALLOWED_POST_URL_SCHEMES: [&str; 3] = ["http", "https", "magnet"]; const BODY_MAX_LENGTH: usize = 10000; const POST_BODY_MAX_LENGTH: usize = 50000; @@ -250,7 +251,7 @@ pub fn check_site_visibility_valid( pub fn check_url_scheme(url: &Option) -> LemmyResult<()> { if let Some(url) = url { - if url.scheme() != "http" && url.scheme() != "https" { + if !ALLOWED_POST_URL_SCHEMES.contains(&url.scheme()) { Err(LemmyErrorType::InvalidUrlScheme.into()) } else { Ok(()) @@ -477,7 +478,11 @@ mod tests { assert!(check_url_scheme(&None).is_ok()); assert!(check_url_scheme(&Some(Url::parse("http://example.com").unwrap())).is_ok()); assert!(check_url_scheme(&Some(Url::parse("https://example.com").unwrap())).is_ok()); + assert!(check_url_scheme(&Some(Url::parse("https://example.com").unwrap())).is_ok()); assert!(check_url_scheme(&Some(Url::parse("ftp://example.com").unwrap())).is_err()); assert!(check_url_scheme(&Some(Url::parse("javascript:void").unwrap())).is_err()); + + let magnet_link="magnet:?xt=urn:btih:4b390af3891e323778959d5abfff4b726510f14c&dn=Ravel%20Complete%20Piano%20Sheet%20Music%20-%20Public%20Domain&tr=udp%3A%2F%2Fopen.tracker.cl%3A1337%2Fannounce"; + assert!(check_url_scheme(&Some(Url::parse(magnet_link).unwrap())).is_ok()); } } From 722d9efc137defbe67f892f46fa1bf7eb2fa83d0 Mon Sep 17 00:00:00 2001 From: HackerNCoder Date: Tue, 24 Oct 2023 14:57:40 +0000 Subject: [PATCH 10/11] Use starts_with for forbidden unicode (Fix #3888) (#4079) * Use starts_with for forbidden unicode (Fix #3888) * Require 3 visible chars in display name * Run cargo fmt and scripts/lint * Undo invisibly_starts_with_at * Remove 3 min chars.count() check for display name --- crates/utils/src/utils/validation.rs | 31 ++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/crates/utils/src/utils/validation.rs b/crates/utils/src/utils/validation.rs index 33c78b654d..46fe9e2d06 100644 --- a/crates/utils/src/utils/validation.rs +++ b/crates/utils/src/utils/validation.rs @@ -99,13 +99,29 @@ pub fn is_valid_actor_name(name: &str, actor_name_max_length: usize) -> LemmyRes } } +fn has_3_permitted_display_chars(name: &str) -> bool { + let mut num_non_fdc: i8 = 0; + for c in name.chars() { + if !FORBIDDEN_DISPLAY_CHARS.contains(&c) { + num_non_fdc += 1; + if num_non_fdc >= 3 { + break; + } + } + } + if num_non_fdc >= 3 { + return true; + } + false +} + // Can't do a regex here, reverse lookarounds not supported pub fn is_valid_display_name(name: &str, actor_name_max_length: usize) -> LemmyResult<()> { - let check = !name.contains(FORBIDDEN_DISPLAY_CHARS) - && !name.starts_with('@') - && name.chars().count() >= 3 + let check = !name.starts_with('@') + && !name.starts_with(FORBIDDEN_DISPLAY_CHARS) && name.chars().count() <= actor_name_max_length - && !has_newline(name); + && !has_newline(name) + && has_3_permitted_display_chars(name); if !check { Err(LemmyErrorType::InvalidDisplayName.into()) } else { @@ -323,6 +339,13 @@ mod tests { let actor_name_max_length = 20; assert!(is_valid_display_name("hello @there", actor_name_max_length).is_ok()); assert!(is_valid_display_name("@hello there", actor_name_max_length).is_err()); + assert!(is_valid_display_name("\u{200d}hello", actor_name_max_length).is_err()); + assert!(is_valid_display_name( + "\u{1f3f3}\u{fe0f}\u{200d}\u{26a7}\u{fe0f}Name", + actor_name_max_length + ) + .is_ok()); + assert!(is_valid_display_name("\u{2003}1\u{ffa0}2\u{200d}", actor_name_max_length).is_err()); // Make sure zero-space with an @ doesn't work assert!( From d55bd2f2bcb7eb981f54be46dc233ef7266dafd8 Mon Sep 17 00:00:00 2001 From: Nutomic Date: Tue, 24 Oct 2023 22:25:52 +0200 Subject: [PATCH 11/11] Allow Arabic and Cyrillic usernames/community names (fixes #1764) (#4083) * Allow Arabic and Cyrillic usernames/community names (fixes #1764) * update comment --- Cargo.lock | 2 -- Cargo.toml | 2 +- api_tests/src/user.spec.ts | 18 +++++++++++++++ crates/utils/src/utils/validation.rs | 33 +++++++++++++++++++++++----- 4 files changed, 46 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 378f6cb2ec..6d0639edad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,8 +11,6 @@ checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" [[package]] name = "activitypub_federation" version = "0.5.0-beta.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "509cbafa1b42e01b7ca76c26298814a6638825df4fd67aef2f4c9d36a39c2b6d" dependencies = [ "activitystreams-kinds", "actix-web", diff --git a/Cargo.toml b/Cargo.toml index a01cc687b7..95652b3116 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -70,7 +70,7 @@ 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 = [ +activitypub_federation = { git = "https://github.com/LemmyNet/activitypub-federation-rust.git", branch = "webfinger-alphabets", default-features = false, features = [ "actix-web", ] } diesel = "2.1.0" diff --git a/api_tests/src/user.spec.ts b/api_tests/src/user.spec.ts index eddf568b84..d651af7e1c 100644 --- a/api_tests/src/user.spec.ts +++ b/api_tests/src/user.spec.ts @@ -129,3 +129,21 @@ test("Requests with invalid auth should be treated as unauthenticated", async () let posts = invalid_auth.getPosts(form); expect((await posts).posts).toBeDefined(); }); + +test("Create user with Arabic name", async () => { + let userRes = await registerUser(alpha, "تجريب"); + expect(userRes.jwt).toBeDefined(); + let user = new LemmyHttp(alphaUrl, { + headers: { Authorization: `Bearer ${userRes.jwt ?? ""}` }, + }); + + let site = await getSite(user); + expect(site.my_user).toBeDefined(); + if (!site.my_user) { + throw "Missing site user"; + } + apShortname = `@${site.my_user.local_user_view.person.name}@lemmy-alpha:8541`; + + let alphaPerson = (await resolvePerson(alpha, apShortname)).person; + expect(alphaPerson).toBeDefined(); +}); diff --git a/crates/utils/src/utils/validation.rs b/crates/utils/src/utils/validation.rs index 46fe9e2d06..36aa2c5f10 100644 --- a/crates/utils/src/utils/validation.rs +++ b/crates/utils/src/utils/validation.rs @@ -4,8 +4,6 @@ use once_cell::sync::Lazy; use regex::{Regex, RegexBuilder}; use url::Url; -static VALID_ACTOR_NAME_REGEX: Lazy = - Lazy::new(|| Regex::new(r"^[a-zA-Z0-9_]{3,}$").expect("compile regex")); static VALID_POST_TITLE_REGEX: Lazy = Lazy::new(|| Regex::new(r".*\S{3,200}.*").expect("compile regex")); @@ -89,10 +87,23 @@ fn has_newline(name: &str) -> bool { } pub fn is_valid_actor_name(name: &str, actor_name_max_length: usize) -> LemmyResult<()> { - let check = name.chars().count() <= actor_name_max_length - && VALID_ACTOR_NAME_REGEX.is_match(name) - && !has_newline(name); - if !check { + static VALID_ACTOR_NAME_REGEX_EN: Lazy = + Lazy::new(|| Regex::new(r"^[a-zA-Z0-9_]{3,}$").expect("compile regex")); + static VALID_ACTOR_NAME_REGEX_AR: Lazy = + Lazy::new(|| Regex::new(r"^[\p{Arabic}0-9_]{3,}$").expect("compile regex")); + static VALID_ACTOR_NAME_REGEX_RU: Lazy = + Lazy::new(|| Regex::new(r"^[\p{Cyrillic}0-9_]{3,}$").expect("compile regex")); + + let check = name.chars().count() <= actor_name_max_length && !has_newline(name); + + // Only allow characters from a single alphabet per username. This avoids problems with lookalike + // characters like `o` which looks identical in Latin and Cyrillic, and can be used to imitate + // other users. Checks for additional alphabets can be added in the same way. + let lang_check = VALID_ACTOR_NAME_REGEX_EN.is_match(name) + || VALID_ACTOR_NAME_REGEX_AR.is_match(name) + || VALID_ACTOR_NAME_REGEX_RU.is_match(name); + + if !check || !lang_check { Err(LemmyErrorType::InvalidName.into()) } else { Ok(()) @@ -329,8 +340,18 @@ mod tests { let actor_name_max_length = 20; assert!(is_valid_actor_name("Hello_98", actor_name_max_length).is_ok()); assert!(is_valid_actor_name("ten", actor_name_max_length).is_ok()); + assert!(is_valid_actor_name("تجريب", actor_name_max_length).is_ok()); + assert!(is_valid_actor_name("تجريب_123", actor_name_max_length).is_ok()); + assert!(is_valid_actor_name("Владимир", actor_name_max_length).is_ok()); + + // mixed scripts + assert!(is_valid_actor_name("تجريب_abc", actor_name_max_length).is_err()); + assert!(is_valid_actor_name("Влад_abc", actor_name_max_length).is_err()); + // dash assert!(is_valid_actor_name("Hello-98", actor_name_max_length).is_err()); + // too short assert!(is_valid_actor_name("a", actor_name_max_length).is_err()); + // empty assert!(is_valid_actor_name("", actor_name_max_length).is_err()); }