mirror of
https://github.com/LemmyNet/lemmy.git
synced 2024-10-01 01:36:12 -04:00
Compare commits
No commits in common. "c4e0c244085b7a6d13b84904420e99675f9caf26" and "4ad1fffee8ab44740597b744744c951eafc5321f" have entirely different histories.
c4e0c24408
...
4ad1fffee8
@ -90,7 +90,7 @@ lemmy_db_views = { version = "=0.19.6-beta.7", path = "./crates/db_views" }
|
||||
lemmy_db_views_actor = { version = "=0.19.6-beta.7", path = "./crates/db_views_actor" }
|
||||
lemmy_db_views_moderator = { version = "=0.19.6-beta.7", path = "./crates/db_views_moderator" }
|
||||
lemmy_federate = { version = "=0.19.6-beta.7", path = "./crates/federate" }
|
||||
activitypub_federation = { version = "0.6.0-alpha2", default-features = false, features = [
|
||||
activitypub_federation = { version = "0.6.0-alpha1", default-features = false, features = [
|
||||
"actix-web",
|
||||
] }
|
||||
diesel = "2.1.6"
|
||||
|
@ -858,26 +858,3 @@ test("Dont send a comment reply to a blocked community", async () => {
|
||||
blockRes = await blockCommunity(beta, newCommunityId, false);
|
||||
expect(blockRes.blocked).toBe(false);
|
||||
});
|
||||
|
||||
/// Fetching a deeply nested comment can lead to stack overflow as all parent comments are also
|
||||
/// fetched recursively. Ensure that it works properly.
|
||||
test("Fetch a deeply nested comment", async () => {
|
||||
let lastComment;
|
||||
for (let i = 0; i < 50; i++) {
|
||||
let commentRes = await createComment(
|
||||
alpha,
|
||||
postOnAlphaRes.post_view.post.id,
|
||||
lastComment?.comment_view.comment.id,
|
||||
);
|
||||
expect(commentRes.comment_view.comment).toBeDefined();
|
||||
lastComment = commentRes;
|
||||
}
|
||||
|
||||
let betaComment = await resolveComment(
|
||||
beta,
|
||||
lastComment!.comment_view.comment,
|
||||
);
|
||||
|
||||
expect(betaComment!.comment!.comment).toBeDefined();
|
||||
expect(betaComment?.comment?.post).toBeDefined();
|
||||
});
|
||||
|
@ -76,7 +76,5 @@ pub async fn leave_admin(
|
||||
admin_oauth_providers: None,
|
||||
blocked_urls,
|
||||
tagline,
|
||||
taglines: vec![],
|
||||
custom_emojis: vec![],
|
||||
}))
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ use serde_with::skip_serializing_none;
|
||||
use ts_rs::TS;
|
||||
use url::Url;
|
||||
|
||||
#[skip_serializing_none]
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[cfg_attr(feature = "full", derive(TS))]
|
||||
#[cfg_attr(feature = "full", ts(export))]
|
||||
@ -20,12 +19,11 @@ pub struct CreateOAuthProvider {
|
||||
pub client_id: String,
|
||||
pub client_secret: String,
|
||||
pub scopes: String,
|
||||
pub auto_verify_email: Option<bool>,
|
||||
pub account_linking_enabled: Option<bool>,
|
||||
pub enabled: Option<bool>,
|
||||
pub auto_verify_email: bool,
|
||||
pub account_linking_enabled: bool,
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
#[skip_serializing_none]
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[cfg_attr(feature = "full", derive(TS))]
|
||||
#[cfg_attr(feature = "full", ts(export))]
|
||||
|
@ -316,8 +316,6 @@ pub struct EditSite {
|
||||
/// The response for a site.
|
||||
pub struct SiteResponse {
|
||||
pub site_view: SiteView,
|
||||
/// deprecated, use field `tagline` or /api/v3/tagline/list
|
||||
pub taglines: Vec<()>,
|
||||
}
|
||||
|
||||
#[skip_serializing_none]
|
||||
@ -332,10 +330,6 @@ pub struct GetSiteResponse {
|
||||
pub my_user: Option<MyUserInfo>,
|
||||
pub all_languages: Vec<Language>,
|
||||
pub discussion_languages: Vec<LanguageId>,
|
||||
/// deprecated, use field `tagline` or /api/v3/tagline/list
|
||||
pub taglines: Vec<()>,
|
||||
/// deprecated, use /api/v3/custom_emoji/list
|
||||
pub custom_emojis: Vec<()>,
|
||||
/// If the site has any taglines, a random one is included here for displaying
|
||||
pub tagline: Option<Tagline>,
|
||||
/// A list of external auth methods your site supports.
|
||||
|
@ -30,9 +30,10 @@ use lemmy_db_views::structs::{LocalUserView, PostView};
|
||||
use lemmy_utils::{
|
||||
error::{LemmyErrorExt, LemmyErrorType, LemmyResult},
|
||||
utils::{mention::scrape_text_for_mentions, validation::is_valid_body_field},
|
||||
MAX_COMMENT_DEPTH_LIMIT,
|
||||
};
|
||||
|
||||
const MAX_COMMENT_DEPTH_LIMIT: usize = 100;
|
||||
|
||||
#[tracing::instrument(skip(context))]
|
||||
pub async fn create_comment(
|
||||
data: Json<CreateComment>,
|
||||
|
@ -142,10 +142,7 @@ pub async fn create_site(
|
||||
local_site_rate_limit_to_rate_limit_config(&site_view.local_site_rate_limit);
|
||||
context.rate_limit_cell().set_config(rate_limit_config);
|
||||
|
||||
Ok(Json(SiteResponse {
|
||||
site_view,
|
||||
taglines: vec![],
|
||||
}))
|
||||
Ok(Json(SiteResponse { site_view }))
|
||||
}
|
||||
|
||||
fn validate_create_payload(local_site: &LocalSite, create_site: &CreateSite) -> LemmyResult<()> {
|
||||
|
@ -59,8 +59,6 @@ pub async fn get_site(
|
||||
tagline,
|
||||
oauth_providers: Some(oauth_providers),
|
||||
admin_oauth_providers: Some(admin_oauth_providers),
|
||||
taglines: vec![],
|
||||
custom_emojis: vec![],
|
||||
})
|
||||
})
|
||||
.await
|
||||
|
@ -196,10 +196,7 @@ pub async fn update_site(
|
||||
local_site_rate_limit_to_rate_limit_config(&site_view.local_site_rate_limit);
|
||||
context.rate_limit_cell().set_config(rate_limit_config);
|
||||
|
||||
Ok(Json(SiteResponse {
|
||||
site_view,
|
||||
taglines: vec![],
|
||||
}))
|
||||
Ok(Json(SiteResponse { site_view }))
|
||||
}
|
||||
|
||||
fn validate_update_payload(local_site: &LocalSite, edit_site: &EditSite) -> LemmyResult<()> {
|
||||
|
@ -79,8 +79,10 @@ impl ActivityHandler for Vote {
|
||||
};
|
||||
|
||||
// Don't allow dislikes for either disabled, or local only votes
|
||||
let downvote_fail = self.kind == VoteType::Dislike && downvote_setting != FederationMode::All;
|
||||
let upvote_fail = self.kind == VoteType::Like && upvote_setting != FederationMode::All;
|
||||
let downvote_fail = self.kind == VoteType::Dislike
|
||||
&& [FederationMode::Disable, FederationMode::Local].contains(&downvote_setting);
|
||||
let upvote_fail = self.kind == VoteType::Like
|
||||
&& [FederationMode::Disable, FederationMode::Local].contains(&upvote_setting);
|
||||
|
||||
if downvote_fail || upvote_fail {
|
||||
// If this is a rejection, undo the vote
|
||||
|
@ -103,16 +103,13 @@ pub async fn import_settings(
|
||||
context: Data<LemmyContext>,
|
||||
) -> LemmyResult<Json<SuccessResponse>> {
|
||||
let person_form = PersonUpdateForm {
|
||||
display_name: data.display_name.clone().map(Some),
|
||||
bio: data.bio.clone().map(Some),
|
||||
matrix_user_id: data.bio.clone().map(Some),
|
||||
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()
|
||||
};
|
||||
// ignore error in case form is empty
|
||||
Person::update(&mut context.pool(), local_user_view.person.id, &person_form)
|
||||
.await
|
||||
.ok();
|
||||
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),
|
||||
@ -315,9 +312,8 @@ where
|
||||
#[expect(clippy::indexing_slicing)]
|
||||
mod tests {
|
||||
|
||||
use crate::api::user_settings_backup::{export_settings, import_settings};
|
||||
use crate::api::user_settings_backup::{export_settings, import_settings, UserSettingsBackup};
|
||||
use activitypub_federation::config::Data;
|
||||
use actix_web::web::Json;
|
||||
use lemmy_api_common::context::LemmyContext;
|
||||
use lemmy_db_schema::{
|
||||
source::{
|
||||
@ -405,6 +401,45 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_settings_partial_import() -> LemmyResult<()> {
|
||||
let context = LemmyContext::init_test_context().await;
|
||||
|
||||
let export_user =
|
||||
create_user("hanna".to_string(), Some("my bio".to_string()), &context).await?;
|
||||
|
||||
let community_form = CommunityInsertForm::new(
|
||||
export_user.person.instance_id,
|
||||
"testcom".to_string(),
|
||||
"testcom".to_string(),
|
||||
"pubkey".to_string(),
|
||||
);
|
||||
let community = Community::create(&mut context.pool(), &community_form).await?;
|
||||
let follower_form = CommunityFollowerForm {
|
||||
community_id: community.id,
|
||||
person_id: export_user.person.id,
|
||||
pending: false,
|
||||
};
|
||||
CommunityFollower::follow(&mut context.pool(), &follower_form).await?;
|
||||
|
||||
let backup = export_settings(export_user.clone(), context.reset_request_count()).await?;
|
||||
|
||||
let import_user = create_user("charles".to_string(), None, &context).await?;
|
||||
|
||||
let backup2 = UserSettingsBackup {
|
||||
followed_communities: backup.followed_communities.clone(),
|
||||
..Default::default()
|
||||
};
|
||||
import_settings(
|
||||
actix_web::web::Json(backup2),
|
||||
import_user.clone(),
|
||||
context.reset_request_count(),
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn disallow_large_backup() -> LemmyResult<()> {
|
||||
@ -440,33 +475,4 @@ mod tests {
|
||||
LocalUser::delete(&mut context.pool(), import_user.local_user.id).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn import_partial_backup() -> LemmyResult<()> {
|
||||
let context = LemmyContext::init_test_context().await;
|
||||
|
||||
let import_user =
|
||||
create_user("hanna".to_string(), Some("my bio".to_string()), &context).await?;
|
||||
|
||||
let backup =
|
||||
serde_json::from_str("{\"bot_account\": true, \"settings\": {\"theme\": \"my_theme\"}}")?;
|
||||
import_settings(
|
||||
Json(backup),
|
||||
import_user.clone(),
|
||||
context.reset_request_count(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let import_user_updated =
|
||||
LocalUserView::read(&mut context.pool(), import_user.local_user.id).await?;
|
||||
// mark as bot account
|
||||
assert!(import_user_updated.person.bot_account);
|
||||
// dont remove existing bio
|
||||
assert_eq!(import_user.person.bio, import_user_updated.person.bio);
|
||||
// local_user can be deserialized without id/person_id fields
|
||||
assert_eq!("my_theme", import_user_updated.local_user.theme);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -20,9 +20,10 @@ use lemmy_db_schema::{
|
||||
source::{community::Community, post::Post},
|
||||
traits::Crud,
|
||||
};
|
||||
use lemmy_utils::{error::LemmyResult, LemmyErrorType, MAX_COMMENT_DEPTH_LIMIT};
|
||||
use lemmy_utils::error::LemmyResult;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::skip_serializing_none;
|
||||
use std::ops::Deref;
|
||||
use url::Url;
|
||||
|
||||
#[skip_serializing_none]
|
||||
@ -57,19 +58,9 @@ impl Note {
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
) -> LemmyResult<(ApubPost, Option<ApubComment>)> {
|
||||
// We use recursion here to fetch the entire comment chain up to the top-level parent. This is
|
||||
// necessary because we need to know the post and parent comment in order to insert a new
|
||||
// comment. However it can also lead to stack overflow when fetching many comments recursively.
|
||||
// To avoid this we check the request count against max comment depth, which based on testing
|
||||
// can be handled without risking stack overflow. This is not a perfect solution, because in
|
||||
// some cases we have to fetch user profiles too, and reach the limit after only 25 comments
|
||||
// or so.
|
||||
// A cleaner solution would be converting the recursion into a loop, but that is tricky.
|
||||
if context.request_count() > MAX_COMMENT_DEPTH_LIMIT as u32 {
|
||||
Err(LemmyErrorType::MaxCommentDepthReached)?;
|
||||
}
|
||||
let parent = self.in_reply_to.dereference(context).await?;
|
||||
match parent {
|
||||
// Fetch parent comment chain in a box, otherwise it can cause a stack overflow.
|
||||
let parent = Box::pin(self.in_reply_to.dereference(context).await?);
|
||||
match parent.deref() {
|
||||
PostOrComment::Post(p) => Ok((p.clone(), None)),
|
||||
PostOrComment::Comment(c) => {
|
||||
let post_id = c.post_id;
|
||||
|
@ -258,9 +258,9 @@ impl Post {
|
||||
post::table
|
||||
.inner_join(person::table)
|
||||
.inner_join(community::table)
|
||||
// find all posts which have scheduled_publish_time that is in the future
|
||||
// find all posts which have scheduled_publish_time that is in the past
|
||||
.filter(post::scheduled_publish_time.is_not_null())
|
||||
.filter(coalesce(post::scheduled_publish_time, now()).gt(now()))
|
||||
.filter(coalesce(post::scheduled_publish_time, now()).lt(now()))
|
||||
// make sure the post and community are still around
|
||||
.filter(not(post::deleted.or(post::removed)))
|
||||
.filter(not(community::removed.or(community::deleted)))
|
||||
@ -414,7 +414,6 @@ mod tests {
|
||||
traits::{Crud, Likeable, Saveable},
|
||||
utils::build_db_pool_for_tests,
|
||||
};
|
||||
use chrono::DateTime;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serial_test::serial;
|
||||
use std::collections::HashSet;
|
||||
@ -457,12 +456,6 @@ mod tests {
|
||||
);
|
||||
let inserted_post2 = Post::create(pool, &new_post2).await.unwrap();
|
||||
|
||||
let new_scheduled_post = PostInsertForm {
|
||||
scheduled_publish_time: Some(DateTime::from_timestamp_nanos(i64::MAX)),
|
||||
..PostInsertForm::new("beans".into(), inserted_person.id, inserted_community.id)
|
||||
};
|
||||
let inserted_scheduled_post = Post::create(pool, &new_scheduled_post).await.unwrap();
|
||||
|
||||
let expected_post = Post {
|
||||
id: inserted_post.id,
|
||||
name: "A test post".into(),
|
||||
@ -542,12 +535,6 @@ mod tests {
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Scheduled post count
|
||||
let scheduled_post_count = Post::user_scheduled_post_count(inserted_person.id, pool)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(1, scheduled_post_count);
|
||||
|
||||
let like_removed = PostLike::remove(pool, inserted_person.id, inserted_post.id)
|
||||
.await
|
||||
.unwrap();
|
||||
@ -564,11 +551,8 @@ mod tests {
|
||||
assert_eq!(2, read_removed);
|
||||
|
||||
let num_deleted = Post::delete(pool, inserted_post.id).await.unwrap()
|
||||
+ Post::delete(pool, inserted_post2.id).await.unwrap()
|
||||
+ Post::delete(pool, inserted_scheduled_post.id)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(3, num_deleted);
|
||||
+ Post::delete(pool, inserted_post2.id).await.unwrap();
|
||||
assert_eq!(2, num_deleted);
|
||||
Community::delete(pool, inserted_community.id)
|
||||
.await
|
||||
.unwrap();
|
||||
|
@ -14,12 +14,11 @@ use serde_with::skip_serializing_none;
|
||||
use ts_rs::TS;
|
||||
|
||||
#[skip_serializing_none]
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Default)]
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "full", derive(Queryable, Selectable, Identifiable, TS))]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = local_user))]
|
||||
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
|
||||
#[cfg_attr(feature = "full", ts(export))]
|
||||
#[serde(default)]
|
||||
/// A local user.
|
||||
pub struct LocalUser {
|
||||
pub id: LocalUserId,
|
||||
|
@ -87,30 +87,39 @@ impl Serialize for PublicOAuthProvider {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
|
||||
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset, TS))]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = oauth_provider))]
|
||||
#[cfg_attr(feature = "full", ts(export))]
|
||||
pub struct OAuthProviderInsertForm {
|
||||
pub display_name: String,
|
||||
#[cfg_attr(feature = "full", ts(type = "string"))]
|
||||
pub issuer: DbUrl,
|
||||
#[cfg_attr(feature = "full", ts(type = "string"))]
|
||||
pub authorization_endpoint: DbUrl,
|
||||
#[cfg_attr(feature = "full", ts(type = "string"))]
|
||||
pub token_endpoint: DbUrl,
|
||||
#[cfg_attr(feature = "full", ts(type = "string"))]
|
||||
pub userinfo_endpoint: DbUrl,
|
||||
pub id_claim: String,
|
||||
pub client_id: String,
|
||||
pub client_secret: String,
|
||||
pub scopes: String,
|
||||
pub auto_verify_email: Option<bool>,
|
||||
pub account_linking_enabled: Option<bool>,
|
||||
pub enabled: Option<bool>,
|
||||
pub auto_verify_email: bool,
|
||||
pub account_linking_enabled: bool,
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
|
||||
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset, TS))]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = oauth_provider))]
|
||||
#[cfg_attr(feature = "full", ts(export))]
|
||||
pub struct OAuthProviderUpdateForm {
|
||||
pub display_name: Option<String>,
|
||||
#[cfg_attr(feature = "full", ts(type = "string"))]
|
||||
pub authorization_endpoint: Option<DbUrl>,
|
||||
#[cfg_attr(feature = "full", ts(type = "string"))]
|
||||
pub token_endpoint: Option<DbUrl>,
|
||||
#[cfg_attr(feature = "full", ts(type = "string"))]
|
||||
pub userinfo_endpoint: Option<DbUrl>,
|
||||
pub id_claim: Option<String>,
|
||||
pub client_secret: Option<String>,
|
||||
|
@ -29,8 +29,6 @@ pub const CACHE_DURATION_FEDERATION: Duration = Duration::from_secs(60);
|
||||
|
||||
pub const CACHE_DURATION_API: Duration = Duration::from_secs(1);
|
||||
|
||||
pub const MAX_COMMENT_DEPTH_LIMIT: usize = 50;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! location_info {
|
||||
() => {
|
||||
|
@ -14,7 +14,7 @@ CREATE TABLE oauth_provider (
|
||||
scopes text NOT NULL,
|
||||
auto_verify_email boolean DEFAULT TRUE NOT NULL,
|
||||
account_linking_enabled boolean DEFAULT FALSE NOT NULL,
|
||||
enabled boolean DEFAULT TRUE NOT NULL,
|
||||
enabled boolean DEFAULT FALSE NOT NULL,
|
||||
published timestamp with time zone DEFAULT now() NOT NULL,
|
||||
updated timestamp with time zone
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user