mirror of
https://github.com/LemmyNet/lemmy.git
synced 2024-10-01 01:36:12 -04:00
Merge remote-tracking branch 'origin/main' into random_community
This commit is contained in:
commit
f5106960f6
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -2538,6 +2538,7 @@ dependencies = [
|
||||
"actix-web",
|
||||
"anyhow",
|
||||
"bcrypt",
|
||||
"chrono",
|
||||
"futures",
|
||||
"lemmy_api_common",
|
||||
"lemmy_db_schema",
|
||||
|
@ -21,16 +21,16 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/node": "^22.0.2",
|
||||
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
||||
"@typescript-eslint/parser": "^8.0.0",
|
||||
"eslint": "^9.8.0",
|
||||
"@types/node": "^22.3.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.1.0",
|
||||
"@typescript-eslint/parser": "^8.1.0",
|
||||
"eslint": "^9.9.0",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"jest": "^29.5.0",
|
||||
"lemmy-js-client": "0.20.0-alpha.11",
|
||||
"prettier": "^3.2.5",
|
||||
"ts-jest": "^29.1.0",
|
||||
"typescript": "^5.5.4",
|
||||
"typescript-eslint": "^8.0.0"
|
||||
"typescript-eslint": "^8.1.0"
|
||||
}
|
||||
}
|
||||
|
@ -12,16 +12,16 @@ importers:
|
||||
specifier: ^29.5.12
|
||||
version: 29.5.12
|
||||
'@types/node':
|
||||
specifier: ^22.0.2
|
||||
specifier: ^22.3.0
|
||||
version: 22.3.0
|
||||
'@typescript-eslint/eslint-plugin':
|
||||
specifier: ^8.0.0
|
||||
specifier: ^8.1.0
|
||||
version: 8.1.0(@typescript-eslint/parser@8.1.0(eslint@9.9.0)(typescript@5.5.4))(eslint@9.9.0)(typescript@5.5.4)
|
||||
'@typescript-eslint/parser':
|
||||
specifier: ^8.0.0
|
||||
specifier: ^8.1.0
|
||||
version: 8.1.0(eslint@9.9.0)(typescript@5.5.4)
|
||||
eslint:
|
||||
specifier: ^9.8.0
|
||||
specifier: ^9.9.0
|
||||
version: 9.9.0
|
||||
eslint-plugin-prettier:
|
||||
specifier: ^5.1.3
|
||||
@ -42,7 +42,7 @@ importers:
|
||||
specifier: ^5.5.4
|
||||
version: 5.5.4
|
||||
typescript-eslint:
|
||||
specifier: ^8.0.0
|
||||
specifier: ^8.1.0
|
||||
version: 8.1.0(eslint@9.9.0)(typescript@5.5.4)
|
||||
|
||||
packages:
|
||||
|
@ -628,7 +628,7 @@ test("Enforce community ban for federated user", async () => {
|
||||
// Alpha tries to make post on beta, but it fails because of ban
|
||||
await expect(
|
||||
createPost(alpha, betaCommunity.community.id),
|
||||
).rejects.toStrictEqual(Error("banned_from_community"));
|
||||
).rejects.toStrictEqual(Error("person_is_banned_from_community"));
|
||||
|
||||
// Unban alpha
|
||||
let unBanAlpha = await banPersonFromCommunity(
|
||||
|
@ -52,15 +52,12 @@ pub async fn add_mod_to_community(
|
||||
// moderator. This is necessary because otherwise the action would be rejected
|
||||
// by the community's home instance.
|
||||
if local_user_view.local_user.admin && !community.local {
|
||||
let is_mod = CommunityModeratorView::is_community_moderator(
|
||||
CommunityModeratorView::check_is_community_moderator(
|
||||
&mut context.pool(),
|
||||
community.id,
|
||||
local_user_view.person.id,
|
||||
)
|
||||
.await?;
|
||||
if !is_mod {
|
||||
Err(LemmyErrorType::NotAModerator)?
|
||||
}
|
||||
}
|
||||
|
||||
// Update in local database
|
||||
|
@ -12,7 +12,7 @@ use lemmy_db_schema::source::{
|
||||
};
|
||||
use lemmy_db_views::structs::LocalUserView;
|
||||
use lemmy_db_views_actor::structs::CommunityView;
|
||||
use lemmy_utils::error::{LemmyErrorType, LemmyResult};
|
||||
use lemmy_utils::error::LemmyResult;
|
||||
|
||||
#[tracing::instrument(skip(context))]
|
||||
pub async fn get_random_community(
|
||||
@ -27,7 +27,6 @@ pub async fn get_random_community(
|
||||
|
||||
let random_community_id = Community::get_random_local_community(&mut context.pool())
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::NotFound)?
|
||||
.id;
|
||||
|
||||
let is_mod_or_admin = is_mod_or_admin_opt(
|
||||
|
@ -265,8 +265,6 @@ pub async fn local_user_view_from_jwt(
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
#[allow(clippy::indexing_slicing)]
|
||||
mod tests {
|
||||
|
||||
use super::*;
|
||||
|
@ -63,9 +63,7 @@ pub async fn save_user_settings(
|
||||
let previous_email = local_user_view.local_user.email.clone().unwrap_or_default();
|
||||
// if email was changed, check that it is not taken and send verification mail
|
||||
if previous_email.deref() != email {
|
||||
if LocalUser::is_email_taken(&mut context.pool(), email).await? {
|
||||
return Err(LemmyErrorType::EmailAlreadyExists)?;
|
||||
}
|
||||
LocalUser::check_is_email_taken(&mut context.pool(), email).await?;
|
||||
send_verification_email(
|
||||
&local_user_view,
|
||||
email,
|
||||
@ -132,7 +130,6 @@ pub async fn save_user_settings(
|
||||
send_notifications_to_email: data.send_notifications_to_email,
|
||||
show_nsfw: data.show_nsfw,
|
||||
blur_nsfw: data.blur_nsfw,
|
||||
auto_expand: data.auto_expand,
|
||||
show_bot_accounts: data.show_bot_accounts,
|
||||
default_post_sort_type,
|
||||
default_comment_sort_type,
|
||||
|
@ -63,7 +63,7 @@ pub async fn leave_admin(
|
||||
let discussion_languages = SiteLanguage::read_local_raw(&mut context.pool()).await?;
|
||||
let oauth_providers = OAuthProvider::get_all_public(&mut context.pool()).await?;
|
||||
let blocked_urls = LocalSiteUrlBlocklist::get_all(&mut context.pool()).await?;
|
||||
let tagline = Tagline::get_random(&mut context.pool()).await?;
|
||||
let tagline = Tagline::get_random(&mut context.pool()).await.ok();
|
||||
|
||||
Ok(Json(GetSiteResponse {
|
||||
site_view,
|
||||
|
@ -34,7 +34,7 @@ use lemmy_db_views::structs::LocalUserView;
|
||||
use lemmy_utils::{error::LemmyResult, LemmyErrorType, CACHE_DURATION_API};
|
||||
use serial_test::serial;
|
||||
|
||||
#[allow(clippy::unwrap_used)]
|
||||
#[expect(clippy::unwrap_used)]
|
||||
async fn create_test_site(context: &Data<LemmyContext>) -> LemmyResult<(Instance, LocalUserView)> {
|
||||
let pool = &mut context.pool();
|
||||
|
||||
@ -109,7 +109,7 @@ async fn signup(
|
||||
Ok((local_user, application))
|
||||
}
|
||||
|
||||
#[allow(clippy::unwrap_used)]
|
||||
#[expect(clippy::unwrap_used)]
|
||||
async fn get_application_statuses(
|
||||
context: &Data<LemmyContext>,
|
||||
admin: LocalUserView,
|
||||
@ -138,10 +138,9 @@ async fn get_application_statuses(
|
||||
Ok((application_count, unread_applications, all_applications))
|
||||
}
|
||||
|
||||
#[allow(clippy::indexing_slicing)]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
#[tokio::test]
|
||||
#[expect(clippy::indexing_slicing)]
|
||||
async fn test_application_approval() -> LemmyResult<()> {
|
||||
let context = LemmyContext::init_test_context().await;
|
||||
let pool = &mut context.pool();
|
||||
|
@ -42,7 +42,7 @@ pub async fn get_sitemap(context: Data<LemmyContext>) -> LemmyResult<HttpRespons
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
#[expect(clippy::unwrap_used)]
|
||||
pub(crate) mod tests {
|
||||
|
||||
use crate::sitemap::generate_urlset;
|
||||
|
@ -29,12 +29,8 @@ impl Claims {
|
||||
let claims =
|
||||
decode::<Claims>(jwt, &key, &validation).with_lemmy_type(LemmyErrorType::NotLoggedIn)?;
|
||||
let user_id = LocalUserId(claims.claims.sub.parse()?);
|
||||
let is_valid = LoginToken::validate(&mut context.pool(), user_id, jwt).await?;
|
||||
if !is_valid {
|
||||
Err(LemmyErrorType::NotLoggedIn)?
|
||||
} else {
|
||||
Ok(user_id)
|
||||
}
|
||||
LoginToken::validate(&mut context.pool(), user_id, jwt).await?;
|
||||
Ok(user_id)
|
||||
}
|
||||
|
||||
pub async fn generate(
|
||||
@ -73,8 +69,7 @@ impl Claims {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
#[allow(clippy::indexing_slicing)]
|
||||
#[expect(clippy::unwrap_used)]
|
||||
mod tests {
|
||||
|
||||
use crate::{claims::Claims, context::LemmyContext};
|
||||
|
@ -84,8 +84,8 @@ pub struct CaptchaResponse {
|
||||
pub struct SaveUserSettings {
|
||||
/// Show nsfw posts.
|
||||
pub show_nsfw: Option<bool>,
|
||||
/// Blur nsfw posts.
|
||||
pub blur_nsfw: Option<bool>,
|
||||
pub auto_expand: Option<bool>,
|
||||
/// Your user's theme.
|
||||
pub theme: Option<String>,
|
||||
/// The default post listing type, usually "local"
|
||||
|
@ -30,6 +30,8 @@ pub struct CreatePost {
|
||||
pub language_id: Option<LanguageId>,
|
||||
/// Instead of fetching a thumbnail, use a custom one.
|
||||
pub custom_thumbnail: Option<String>,
|
||||
/// Time when this post should be scheduled. Null means publish immediately.
|
||||
pub scheduled_publish_time: Option<i64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
@ -124,6 +126,8 @@ pub struct EditPost {
|
||||
pub language_id: Option<LanguageId>,
|
||||
/// Instead of fetching a thumbnail, use a custom one.
|
||||
pub custom_thumbnail: Option<String>,
|
||||
/// Time when this post should be scheduled. Null means publish immediately.
|
||||
pub scheduled_publish_time: Option<i64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
|
||||
|
@ -471,8 +471,7 @@ pub async fn replace_image(
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
#[allow(clippy::indexing_slicing)]
|
||||
#[expect(clippy::unwrap_used)]
|
||||
mod tests {
|
||||
|
||||
use crate::{
|
||||
|
@ -78,7 +78,7 @@ pub struct Search {
|
||||
pub listing_type: Option<ListingType>,
|
||||
pub page: Option<i64>,
|
||||
pub limit: Option<i64>,
|
||||
pub post_title_only: Option<bool>,
|
||||
pub title_only: Option<bool>,
|
||||
pub post_url_only: Option<bool>,
|
||||
pub saved_only: Option<bool>,
|
||||
pub liked_only: Option<bool>,
|
||||
|
@ -73,13 +73,7 @@ pub async fn is_mod_or_admin(
|
||||
community_id: CommunityId,
|
||||
) -> LemmyResult<()> {
|
||||
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 {
|
||||
Ok(())
|
||||
}
|
||||
CommunityView::check_is_mod_or_admin(pool, person.id, community_id).await
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
@ -110,13 +104,7 @@ pub async fn check_community_mod_of_any_or_admin_action(
|
||||
let person = &local_user_view.person;
|
||||
|
||||
check_user_valid(person)?;
|
||||
|
||||
let is_mod_of_any_or_admin = CommunityView::is_mod_of_any_or_admin(pool, person.id).await?;
|
||||
if !is_mod_of_any_or_admin {
|
||||
Err(LemmyErrorType::NotAModOrAdmin)?
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
CommunityView::check_is_mod_of_any_or_admin(pool, person.id).await
|
||||
}
|
||||
|
||||
pub fn is_admin(local_user_view: &LocalUserView) -> LemmyResult<()> {
|
||||
@ -242,7 +230,7 @@ pub async fn check_community_user_action(
|
||||
) -> LemmyResult<()> {
|
||||
check_user_valid(person)?;
|
||||
check_community_deleted_removed(community_id, pool).await?;
|
||||
check_community_ban(person, community_id, pool).await?;
|
||||
CommunityPersonBanView::check(pool, person.id, community_id).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -257,19 +245,6 @@ async fn check_community_deleted_removed(
|
||||
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
|
||||
@ -281,7 +256,7 @@ pub async fn check_community_mod_action(
|
||||
pool: &mut DbPool<'_>,
|
||||
) -> LemmyResult<()> {
|
||||
is_mod_or_admin(pool, person, community_id).await?;
|
||||
check_community_ban(person, community_id, pool).await?;
|
||||
CommunityPersonBanView::check(pool, person.id, community_id).await?;
|
||||
|
||||
// it must be possible to restore deleted community
|
||||
if !allow_deleted {
|
||||
@ -307,51 +282,6 @@ pub fn check_comment_deleted_or_removed(comment: &Comment) -> LemmyResult<()> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Throws an error if a recipient has blocked a person.
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub async fn check_person_block(
|
||||
my_id: PersonId,
|
||||
potential_blocker_id: PersonId,
|
||||
pool: &mut DbPool<'_>,
|
||||
) -> LemmyResult<()> {
|
||||
let is_blocked = PersonBlock::read(pool, potential_blocker_id, my_id).await?;
|
||||
if is_blocked {
|
||||
Err(LemmyErrorType::PersonIsBlocked)?
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Throws an error if a recipient has blocked a community.
|
||||
#[tracing::instrument(skip_all)]
|
||||
async fn check_community_block(
|
||||
community_id: CommunityId,
|
||||
person_id: PersonId,
|
||||
pool: &mut DbPool<'_>,
|
||||
) -> LemmyResult<()> {
|
||||
let is_blocked = CommunityBlock::read(pool, person_id, community_id).await?;
|
||||
if is_blocked {
|
||||
Err(LemmyErrorType::CommunityIsBlocked)?
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Throws an error if a recipient has blocked an instance.
|
||||
#[tracing::instrument(skip_all)]
|
||||
async fn check_instance_block(
|
||||
instance_id: InstanceId,
|
||||
person_id: PersonId,
|
||||
pool: &mut DbPool<'_>,
|
||||
) -> LemmyResult<()> {
|
||||
let is_blocked = InstanceBlock::read(pool, person_id, instance_id).await?;
|
||||
if is_blocked {
|
||||
Err(LemmyErrorType::InstanceIsBlocked)?
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub async fn check_person_instance_community_block(
|
||||
my_id: PersonId,
|
||||
@ -360,9 +290,9 @@ pub async fn check_person_instance_community_block(
|
||||
community_id: CommunityId,
|
||||
pool: &mut DbPool<'_>,
|
||||
) -> LemmyResult<()> {
|
||||
check_person_block(my_id, potential_blocker_id, pool).await?;
|
||||
check_instance_block(community_instance_id, potential_blocker_id, pool).await?;
|
||||
check_community_block(community_id, potential_blocker_id, pool).await?;
|
||||
PersonBlock::read(pool, potential_blocker_id, my_id).await?;
|
||||
InstanceBlock::read(pool, potential_blocker_id, community_instance_id).await?;
|
||||
CommunityBlock::read(pool, potential_blocker_id, community_id).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -846,12 +776,13 @@ pub async fn remove_or_restore_user_data_in_community(
|
||||
|
||||
// Comments
|
||||
// TODO Diesel doesn't allow updates with joins, so this has to be a loop
|
||||
let site = Site::read_local(pool).await?;
|
||||
let comments = CommentQuery {
|
||||
creator_id: Some(banned_person_id),
|
||||
community_id: Some(community_id),
|
||||
..Default::default()
|
||||
}
|
||||
.list(pool)
|
||||
.list(&site, pool)
|
||||
.await?;
|
||||
|
||||
for comment_view in &comments {
|
||||
@ -1136,8 +1067,7 @@ fn build_proxied_image_url(
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
#[allow(clippy::indexing_slicing)]
|
||||
#[expect(clippy::unwrap_used)]
|
||||
mod tests {
|
||||
|
||||
use super::*;
|
||||
|
@ -27,6 +27,7 @@ futures.workspace = true
|
||||
uuid = { workspace = true }
|
||||
moka.workspace = true
|
||||
anyhow.workspace = true
|
||||
chrono.workspace = true
|
||||
webmention = "0.6.0"
|
||||
accept-language = "3.1.0"
|
||||
serde_json = { workspace = true }
|
||||
|
@ -1,3 +1,4 @@
|
||||
use super::convert_published_time;
|
||||
use activitypub_federation::config::Data;
|
||||
use actix_web::web::Json;
|
||||
use lemmy_api_common::{
|
||||
@ -95,15 +96,12 @@ pub async fn create_post(
|
||||
let community = Community::read(&mut context.pool(), community_id).await?;
|
||||
if community.posting_restricted_to_mods {
|
||||
let community_id = data.community_id;
|
||||
let is_mod = CommunityModeratorView::is_community_moderator(
|
||||
CommunityModeratorView::check_is_community_moderator(
|
||||
&mut context.pool(),
|
||||
community_id,
|
||||
local_user_view.local_user.person_id,
|
||||
)
|
||||
.await?;
|
||||
if !is_mod {
|
||||
Err(LemmyErrorType::OnlyModsCanPostInCommunity)?
|
||||
}
|
||||
}
|
||||
|
||||
// Only need to check if language is allowed in case user set it explicitly. When using default
|
||||
@ -128,12 +126,15 @@ pub async fn create_post(
|
||||
}
|
||||
};
|
||||
|
||||
let scheduled_publish_time =
|
||||
convert_published_time(data.scheduled_publish_time, &local_user_view, &context).await?;
|
||||
let post_form = PostInsertForm {
|
||||
url: url.map(Into::into),
|
||||
body,
|
||||
alt_text: data.alt_text.clone(),
|
||||
nsfw: data.nsfw,
|
||||
language_id,
|
||||
scheduled_publish_time,
|
||||
..PostInsertForm::new(
|
||||
data.name.trim().to_string(),
|
||||
local_user_view.person.id,
|
||||
@ -145,10 +146,16 @@ pub async fn create_post(
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntCreatePost)?;
|
||||
|
||||
let federate_post = if scheduled_publish_time.is_none() {
|
||||
send_webmention(inserted_post.clone(), community);
|
||||
|post| Some(SendActivityData::CreatePost(post))
|
||||
} else {
|
||||
|_| None
|
||||
};
|
||||
generate_post_link_metadata(
|
||||
inserted_post.clone(),
|
||||
custom_thumbnail.map(Into::into),
|
||||
|post| Some(SendActivityData::CreatePost(post)),
|
||||
federate_post,
|
||||
context.reset_request_count(),
|
||||
)
|
||||
.await?;
|
||||
@ -168,11 +175,14 @@ pub async fn create_post(
|
||||
|
||||
mark_post_as_read(person_id, post_id, &mut context.pool()).await?;
|
||||
|
||||
if let Some(url) = inserted_post.url.clone() {
|
||||
build_post_response(&context, community_id, local_user_view, post_id).await
|
||||
}
|
||||
|
||||
pub fn send_webmention(post: Post, community: Community) {
|
||||
if let Some(url) = post.url.clone() {
|
||||
if community.visibility == CommunityVisibility::Public {
|
||||
spawn_try_task(async move {
|
||||
let mut webmention =
|
||||
Webmention::new::<Url>(inserted_post.ap_id.clone().into(), url.clone().into())?;
|
||||
let mut webmention = Webmention::new::<Url>(post.ap_id.clone().into(), url.clone().into())?;
|
||||
webmention.set_checked(true);
|
||||
match webmention
|
||||
.send()
|
||||
@ -186,6 +196,4 @@ pub async fn create_post(
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
build_post_response(&context, community_id, local_user_view, post_id).await
|
||||
}
|
||||
|
@ -1,5 +1,38 @@
|
||||
use chrono::{DateTime, TimeZone, Utc};
|
||||
use lemmy_api_common::context::LemmyContext;
|
||||
use lemmy_db_schema::source::post::Post;
|
||||
use lemmy_db_views::structs::LocalUserView;
|
||||
use lemmy_utils::{error::LemmyResult, LemmyErrorType};
|
||||
|
||||
pub mod create;
|
||||
pub mod delete;
|
||||
pub mod read;
|
||||
pub mod remove;
|
||||
pub mod update;
|
||||
|
||||
async fn convert_published_time(
|
||||
scheduled_publish_time: Option<i64>,
|
||||
local_user_view: &LocalUserView,
|
||||
context: &LemmyContext,
|
||||
) -> LemmyResult<Option<DateTime<Utc>>> {
|
||||
const MAX_SCHEDULED_POSTS: i64 = 10;
|
||||
if let Some(scheduled_publish_time) = scheduled_publish_time {
|
||||
let converted = Utc
|
||||
.timestamp_opt(scheduled_publish_time, 0)
|
||||
.single()
|
||||
.ok_or(LemmyErrorType::InvalidUnixTime)?;
|
||||
if converted < Utc::now() {
|
||||
Err(LemmyErrorType::PostScheduleTimeMustBeInFuture)?;
|
||||
}
|
||||
if !local_user_view.local_user.admin {
|
||||
let count =
|
||||
Post::user_scheduled_post_count(local_user_view.person.id, &mut context.pool()).await?;
|
||||
if count >= MAX_SCHEDULED_POSTS {
|
||||
Err(LemmyErrorType::TooManyScheduledPosts)?;
|
||||
}
|
||||
}
|
||||
Ok(Some(converted))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
use super::{convert_published_time, create::send_webmention};
|
||||
use activitypub_federation::config::Data;
|
||||
use actix_web::web::Json;
|
||||
use lemmy_api_common::{
|
||||
@ -16,6 +17,7 @@ use lemmy_api_common::{
|
||||
use lemmy_db_schema::{
|
||||
source::{
|
||||
actor_language::CommunityLanguage,
|
||||
community::Community,
|
||||
local_site::LocalSite,
|
||||
post::{Post, PostUpdateForm},
|
||||
},
|
||||
@ -107,6 +109,21 @@ pub async fn update_post(
|
||||
)
|
||||
.await?;
|
||||
|
||||
// handle changes to scheduled_publish_time
|
||||
let scheduled_publish_time = match (
|
||||
orig_post.scheduled_publish_time,
|
||||
data.scheduled_publish_time,
|
||||
) {
|
||||
// schedule time can be changed if post is still scheduled (and not published yet)
|
||||
(Some(_), Some(_)) => {
|
||||
Some(convert_published_time(data.scheduled_publish_time, &local_user_view, &context).await?)
|
||||
}
|
||||
// post was scheduled, gets changed to publish immediately
|
||||
(Some(_), None) => Some(None),
|
||||
// unchanged
|
||||
(_, _) => None,
|
||||
};
|
||||
|
||||
let post_form = PostUpdateForm {
|
||||
name: data.name.clone(),
|
||||
url,
|
||||
@ -115,6 +132,7 @@ pub async fn update_post(
|
||||
nsfw: data.nsfw,
|
||||
language_id: data.language_id,
|
||||
updated: Some(Some(naive_now())),
|
||||
scheduled_publish_time,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
@ -123,13 +141,36 @@ pub async fn update_post(
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntUpdatePost)?;
|
||||
|
||||
generate_post_link_metadata(
|
||||
updated_post.clone(),
|
||||
custom_thumbnail.flatten().map(Into::into),
|
||||
|post| Some(SendActivityData::UpdatePost(post)),
|
||||
context.reset_request_count(),
|
||||
)
|
||||
.await?;
|
||||
// send out federation/webmention if necessary
|
||||
match (
|
||||
orig_post.scheduled_publish_time,
|
||||
data.scheduled_publish_time,
|
||||
) {
|
||||
// schedule was removed, send create activity and webmention
|
||||
(Some(_), None) => {
|
||||
let community = Community::read(&mut context.pool(), orig_post.community_id).await?;
|
||||
send_webmention(updated_post.clone(), community);
|
||||
generate_post_link_metadata(
|
||||
updated_post.clone(),
|
||||
custom_thumbnail.flatten().map(Into::into),
|
||||
|post| Some(SendActivityData::CreatePost(post)),
|
||||
context.reset_request_count(),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
// post was already public, send update
|
||||
(None, _) => {
|
||||
generate_post_link_metadata(
|
||||
updated_post.clone(),
|
||||
custom_thumbnail.flatten().map(Into::into),
|
||||
|post| Some(SendActivityData::UpdatePost(post)),
|
||||
context.reset_request_count(),
|
||||
)
|
||||
.await?
|
||||
}
|
||||
// schedule was changed, do nothing
|
||||
(Some(_), Some(_)) => {}
|
||||
};
|
||||
|
||||
build_post_response(
|
||||
context.deref(),
|
||||
|
@ -5,7 +5,6 @@ use lemmy_api_common::{
|
||||
private_message::{CreatePrivateMessage, PrivateMessageResponse},
|
||||
send_activity::{ActivityChannel, SendActivityData},
|
||||
utils::{
|
||||
check_person_block,
|
||||
get_interface_language,
|
||||
get_url_blocklist,
|
||||
local_site_to_slur_regex,
|
||||
@ -16,6 +15,7 @@ use lemmy_api_common::{
|
||||
use lemmy_db_schema::{
|
||||
source::{
|
||||
local_site::LocalSite,
|
||||
person_block::PersonBlock,
|
||||
private_message::{PrivateMessage, PrivateMessageInsertForm},
|
||||
},
|
||||
traits::Crud,
|
||||
@ -39,10 +39,10 @@ pub async fn create_private_message(
|
||||
let content = process_markdown(&data.content, &slur_regex, &url_blocklist, &context).await?;
|
||||
is_valid_body_field(&content, false)?;
|
||||
|
||||
check_person_block(
|
||||
local_user_view.person.id,
|
||||
data.recipient_id,
|
||||
PersonBlock::read(
|
||||
&mut context.pool(),
|
||||
data.recipient_id,
|
||||
local_user_view.person.id,
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
@ -189,8 +189,6 @@ fn validate_create_payload(local_site: &LocalSite, create_site: &CreateSite) ->
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
#[allow(clippy::indexing_slicing)]
|
||||
mod tests {
|
||||
|
||||
use crate::site::create::validate_create_payload;
|
||||
|
@ -48,8 +48,6 @@ fn not_zero(val: Option<i32>) -> Option<i32> {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
#[allow(clippy::indexing_slicing)]
|
||||
mod tests {
|
||||
|
||||
use crate::site::{application_question_check, not_zero, site_default_post_listing_type_check};
|
||||
|
@ -43,7 +43,7 @@ pub async fn get_site(
|
||||
let all_languages = Language::read_all(&mut context.pool()).await?;
|
||||
let discussion_languages = SiteLanguage::read_local_raw(&mut context.pool()).await?;
|
||||
let blocked_urls = LocalSiteUrlBlocklist::get_all(&mut context.pool()).await?;
|
||||
let tagline = Tagline::get_random(&mut context.pool()).await?;
|
||||
let tagline = Tagline::get_random(&mut context.pool()).await.ok();
|
||||
let admin_oauth_providers = OAuthProvider::get_all(&mut context.pool()).await?;
|
||||
let oauth_providers =
|
||||
OAuthProvider::convert_providers_to_public(admin_oauth_providers.clone());
|
||||
|
@ -241,8 +241,6 @@ fn validate_update_payload(local_site: &LocalSite, edit_site: &EditSite) -> Lemm
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
#[allow(clippy::indexing_slicing)]
|
||||
mod tests {
|
||||
|
||||
use crate::site::update::validate_update_payload;
|
||||
|
@ -92,36 +92,25 @@ pub async fn register(
|
||||
}
|
||||
|
||||
if local_site.site_setup && local_site.captcha_enabled {
|
||||
if let Some(captcha_uuid) = &data.captcha_uuid {
|
||||
let uuid = uuid::Uuid::parse_str(captcha_uuid)?;
|
||||
let check = CaptchaAnswer::check_captcha(
|
||||
&mut context.pool(),
|
||||
CheckCaptchaAnswer {
|
||||
uuid,
|
||||
answer: data.captcha_answer.clone().unwrap_or_default(),
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
if !check {
|
||||
Err(LemmyErrorType::CaptchaIncorrect)?
|
||||
}
|
||||
} else {
|
||||
Err(LemmyErrorType::CaptchaIncorrect)?
|
||||
}
|
||||
let uuid = uuid::Uuid::parse_str(&data.captcha_uuid.clone().unwrap_or_default())?;
|
||||
CaptchaAnswer::check_captcha(
|
||||
&mut context.pool(),
|
||||
CheckCaptchaAnswer {
|
||||
uuid,
|
||||
answer: data.captcha_answer.clone().unwrap_or_default(),
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
let slur_regex = local_site_to_slur_regex(&local_site);
|
||||
check_slurs(&data.username, &slur_regex)?;
|
||||
check_slurs_opt(&data.answer, &slur_regex)?;
|
||||
|
||||
if Person::is_username_taken(&mut context.pool(), &data.username).await? {
|
||||
return Err(LemmyErrorType::UsernameAlreadyExists)?;
|
||||
}
|
||||
Person::check_username_taken(&mut context.pool(), &data.username).await?;
|
||||
|
||||
if let Some(email) = &data.email {
|
||||
if LocalUser::is_email_taken(&mut context.pool(), email).await? {
|
||||
Err(LemmyErrorType::EmailAlreadyExists)?
|
||||
}
|
||||
LocalUser::check_is_email_taken(&mut context.pool(), email).await?;
|
||||
}
|
||||
|
||||
// We have to create both a person, and local_user
|
||||
@ -338,9 +327,7 @@ pub async fn authenticate_with_oauth(
|
||||
check_slurs(username, &slur_regex)?;
|
||||
check_slurs_opt(&data.answer, &slur_regex)?;
|
||||
|
||||
if Person::is_username_taken(&mut context.pool(), username).await? {
|
||||
return Err(LemmyErrorType::UsernameAlreadyExists)?;
|
||||
}
|
||||
Person::check_username_taken(&mut context.pool(), username).await?;
|
||||
|
||||
// We have to create a person, a local_user, and an oauth_account
|
||||
person = create_person(
|
||||
|
@ -213,15 +213,13 @@ async fn can_accept_activity_in_community(
|
||||
context: &Data<LemmyContext>,
|
||||
) -> LemmyResult<()> {
|
||||
if let Some(community) = community {
|
||||
if !community.local
|
||||
&& !CommunityFollower::has_local_followers(&mut context.pool(), community.id).await?
|
||||
{
|
||||
Err(LemmyErrorType::CommunityHasNoFollowers)?
|
||||
}
|
||||
// Local only community can't federate
|
||||
if community.visibility != CommunityVisibility::Public {
|
||||
return Err(LemmyErrorType::NotFound.into());
|
||||
}
|
||||
if !community.local {
|
||||
CommunityFollower::check_has_local_followers(&mut context.pool(), community.id).await?
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -87,12 +87,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?;
|
||||
if is_banned {
|
||||
Err(LemmyErrorType::PersonIsBannedFromCommunity)?
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
CommunityPersonBanView::check(&mut context.pool(), person_id, community_id).await
|
||||
}
|
||||
|
||||
/// Verify that mod action in community was performed by a moderator.
|
||||
@ -106,14 +101,6 @@ pub(crate) async fn verify_mod_action(
|
||||
community: &Community,
|
||||
context: &Data<LemmyContext>,
|
||||
) -> LemmyResult<()> {
|
||||
let mod_ = mod_id.dereference(context).await?;
|
||||
|
||||
let is_mod_or_admin =
|
||||
CommunityView::is_mod_or_admin(&mut context.pool(), mod_.id, community.id).await?;
|
||||
if is_mod_or_admin {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// mod action comes from the same instance as the community, so it was presumably done
|
||||
// by an instance admin.
|
||||
// TODO: federate instance admin status and check it here
|
||||
@ -121,7 +108,8 @@ pub(crate) async fn verify_mod_action(
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Err(LemmyErrorType::NotAModerator)?
|
||||
let mod_ = mod_id.dereference(context).await?;
|
||||
CommunityView::check_is_mod_or_admin(&mut context.pool(), mod_.id, community.id).await
|
||||
}
|
||||
|
||||
pub(crate) fn verify_is_public(to: &[Url], cc: &[Url]) -> LemmyResult<()> {
|
||||
|
@ -123,7 +123,6 @@ impl InCommunity for AnnouncableActivities {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::indexing_slicing)]
|
||||
mod tests {
|
||||
|
||||
use crate::{
|
||||
|
@ -12,10 +12,13 @@ use lemmy_api_common::{
|
||||
utils::check_private_instance,
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
source::{comment::Comment, community::Community, local_site::LocalSite},
|
||||
source::{comment::Comment, community::Community},
|
||||
traits::Crud,
|
||||
};
|
||||
use lemmy_db_views::{comment_view::CommentQuery, structs::LocalUserView};
|
||||
use lemmy_db_views::{
|
||||
comment_view::CommentQuery,
|
||||
structs::{LocalUserView, SiteView},
|
||||
};
|
||||
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||
|
||||
#[tracing::instrument(skip(context))]
|
||||
@ -24,8 +27,8 @@ pub async fn list_comments(
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: Option<LocalUserView>,
|
||||
) -> LemmyResult<Json<GetCommentsResponse>> {
|
||||
let local_site = LocalSite::read(&mut context.pool()).await?;
|
||||
check_private_instance(&local_user_view, &local_site)?;
|
||||
let site_view = SiteView::read_local(&mut context.pool()).await?;
|
||||
check_private_instance(&local_user_view, &site_view.local_site)?;
|
||||
|
||||
let community_id = if let Some(name) = &data.community_name {
|
||||
Some(
|
||||
@ -40,7 +43,7 @@ pub async fn list_comments(
|
||||
let sort = Some(comment_sort_type_with_default(
|
||||
data.sort,
|
||||
local_user_ref,
|
||||
&local_site,
|
||||
&site_view.local_site,
|
||||
));
|
||||
let max_depth = data.max_depth;
|
||||
let saved_only = data.saved_only;
|
||||
@ -58,7 +61,7 @@ pub async fn list_comments(
|
||||
let listing_type = Some(listing_type_with_default(
|
||||
data.type_,
|
||||
local_user_view.as_ref().map(|u| &u.local_user),
|
||||
&local_site,
|
||||
&site_view.local_site,
|
||||
community_id,
|
||||
));
|
||||
|
||||
@ -88,7 +91,7 @@ pub async fn list_comments(
|
||||
limit,
|
||||
..Default::default()
|
||||
}
|
||||
.list(&mut context.pool())
|
||||
.list(&site_view.site, &mut context.pool())
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntGetComments)?;
|
||||
|
||||
|
@ -85,7 +85,7 @@ pub async fn read_person(
|
||||
creator_id,
|
||||
..Default::default()
|
||||
}
|
||||
.list(&mut context.pool())
|
||||
.list(&local_site.site, &mut context.pool())
|
||||
.await?;
|
||||
|
||||
let moderates = CommunityModeratorView::for_person(
|
||||
|
@ -47,7 +47,7 @@ pub async fn search(
|
||||
listing_type,
|
||||
page,
|
||||
limit,
|
||||
post_title_only,
|
||||
title_only,
|
||||
post_url_only,
|
||||
saved_only,
|
||||
liked_only,
|
||||
@ -78,7 +78,7 @@ pub async fn search(
|
||||
search_term: Some(q.clone()),
|
||||
page,
|
||||
limit,
|
||||
title_only: post_title_only,
|
||||
title_only,
|
||||
url_only: post_url_only,
|
||||
liked_only,
|
||||
disliked_only,
|
||||
@ -105,6 +105,7 @@ pub async fn search(
|
||||
sort,
|
||||
listing_type,
|
||||
search_term: Some(q.clone()),
|
||||
title_only,
|
||||
local_user,
|
||||
is_mod_or_admin: is_admin,
|
||||
page,
|
||||
@ -127,7 +128,9 @@ pub async fn search(
|
||||
.await?;
|
||||
}
|
||||
SearchType::Comments => {
|
||||
comments = comment_query.list(&mut context.pool()).await?;
|
||||
comments = comment_query
|
||||
.list(&local_site.site, &mut context.pool())
|
||||
.await?;
|
||||
}
|
||||
SearchType::Communities => {
|
||||
communities = community_query
|
||||
@ -146,7 +149,9 @@ pub async fn search(
|
||||
.list(&local_site.site, &mut context.pool())
|
||||
.await?;
|
||||
|
||||
comments = comment_query.list(&mut context.pool()).await?;
|
||||
comments = comment_query
|
||||
.list(&local_site.site, &mut context.pool())
|
||||
.await?;
|
||||
|
||||
communities = if community_or_creator_included {
|
||||
vec![]
|
||||
|
@ -127,7 +127,6 @@ pub async fn import_settings(
|
||||
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()
|
||||
@ -308,8 +307,9 @@ where
|
||||
});
|
||||
Ok(failed_items.into_iter().join(","))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::indexing_slicing)]
|
||||
#[expect(clippy::indexing_slicing)]
|
||||
mod tests {
|
||||
|
||||
use crate::api::user_settings_backup::{export_settings, import_settings, UserSettingsBackup};
|
||||
|
@ -98,7 +98,7 @@ impl Collection for ApubCommunityModerators {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::indexing_slicing)]
|
||||
#[expect(clippy::indexing_slicing)]
|
||||
mod tests {
|
||||
|
||||
use super::*;
|
||||
|
@ -120,8 +120,7 @@ pub(crate) async fn get_apub_community_featured(
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
#[allow(clippy::indexing_slicing)]
|
||||
#[expect(clippy::unwrap_used)]
|
||||
pub(crate) mod tests {
|
||||
|
||||
use super::*;
|
||||
|
@ -39,7 +39,7 @@ use lemmy_db_schema::{
|
||||
};
|
||||
use lemmy_db_views_actor::structs::CommunityModeratorView;
|
||||
use lemmy_utils::{
|
||||
error::{LemmyError, LemmyErrorType, LemmyResult},
|
||||
error::{LemmyError, LemmyResult},
|
||||
spawn_try_task,
|
||||
utils::{
|
||||
markdown::markdown_to_html,
|
||||
@ -180,15 +180,12 @@ impl Object for ApubPost {
|
||||
let creator = page.creator()?.dereference(context).await?;
|
||||
let community = page.community(context).await?;
|
||||
if community.posting_restricted_to_mods {
|
||||
let is_mod = CommunityModeratorView::is_community_moderator(
|
||||
CommunityModeratorView::check_is_community_moderator(
|
||||
&mut context.pool(),
|
||||
community.id,
|
||||
creator.id,
|
||||
)
|
||||
.await?;
|
||||
if !is_mod {
|
||||
Err(LemmyErrorType::OnlyModsCanPostInCommunity)?
|
||||
}
|
||||
}
|
||||
let mut name = page
|
||||
.name
|
||||
|
@ -15,12 +15,13 @@ use activitypub_federation::{
|
||||
use chrono::{DateTime, Utc};
|
||||
use lemmy_api_common::{
|
||||
context::LemmyContext,
|
||||
utils::{check_person_block, get_url_blocklist, local_site_opt_to_slur_regex, process_markdown},
|
||||
utils::{get_url_blocklist, local_site_opt_to_slur_regex, process_markdown},
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
source::{
|
||||
local_site::LocalSite,
|
||||
person::Person,
|
||||
person_block::PersonBlock,
|
||||
private_message::{PrivateMessage, PrivateMessageInsertForm},
|
||||
},
|
||||
traits::Crud,
|
||||
@ -126,7 +127,7 @@ impl Object for ApubPrivateMessage {
|
||||
) -> LemmyResult<ApubPrivateMessage> {
|
||||
let creator = note.attributed_to.dereference(context).await?;
|
||||
let recipient = note.to[0].dereference(context).await?;
|
||||
check_person_block(creator.id, recipient.id, &mut context.pool()).await?;
|
||||
PersonBlock::read(&mut context.pool(), recipient.id, creator.id).await?;
|
||||
|
||||
let local_site = LocalSite::read(&mut context.pool()).await.ok();
|
||||
let slur_regex = &local_site_opt_to_slur_regex(&local_site);
|
||||
|
@ -75,7 +75,7 @@ impl<S: ValidGrouping<(), IsAggregate = is_aggregate::No>> ValidGrouping<()>
|
||||
type IsAggregate = is_aggregate::No;
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
#[expect(non_camel_case_types)]
|
||||
#[derive(QueryId, Clone, Copy, Debug)]
|
||||
pub struct current_value;
|
||||
|
||||
|
@ -30,8 +30,7 @@ impl CommentAggregates {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
#[allow(clippy::indexing_slicing)]
|
||||
#[expect(clippy::unwrap_used)]
|
||||
mod tests {
|
||||
|
||||
use crate::{
|
||||
|
@ -36,8 +36,7 @@ impl CommunityAggregates {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
#[allow(clippy::indexing_slicing)]
|
||||
#[expect(clippy::unwrap_used)]
|
||||
mod tests {
|
||||
|
||||
use crate::{
|
||||
|
@ -20,8 +20,7 @@ impl PersonAggregates {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
#[allow(clippy::indexing_slicing)]
|
||||
#[expect(clippy::unwrap_used)]
|
||||
mod tests {
|
||||
|
||||
use crate::{
|
||||
|
@ -49,8 +49,8 @@ impl PostAggregates {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
#[allow(clippy::indexing_slicing)]
|
||||
#[expect(clippy::unwrap_used)]
|
||||
|
||||
mod tests {
|
||||
|
||||
use crate::{
|
||||
|
@ -15,8 +15,8 @@ impl SiteAggregates {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
#[allow(clippy::indexing_slicing)]
|
||||
#[expect(clippy::unwrap_used)]
|
||||
|
||||
mod tests {
|
||||
|
||||
use crate::{
|
||||
|
@ -58,8 +58,7 @@ impl ReceivedActivity {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
#[allow(clippy::indexing_slicing)]
|
||||
#[expect(clippy::unwrap_used)]
|
||||
mod tests {
|
||||
|
||||
use super::*;
|
||||
|
@ -392,8 +392,8 @@ async fn convert_read_languages(
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
#[allow(clippy::indexing_slicing)]
|
||||
#[expect(clippy::unwrap_used)]
|
||||
#[expect(clippy::indexing_slicing)]
|
||||
mod tests {
|
||||
|
||||
use super::*;
|
||||
|
@ -13,6 +13,7 @@ use diesel::{
|
||||
QueryDsl,
|
||||
};
|
||||
use diesel_async::RunQueryDsl;
|
||||
use lemmy_utils::{error::LemmyResult, LemmyErrorType};
|
||||
|
||||
impl CaptchaAnswer {
|
||||
pub async fn insert(pool: &mut DbPool<'_>, captcha: &CaptchaAnswerForm) -> Result<Self, Error> {
|
||||
@ -27,7 +28,7 @@ impl CaptchaAnswer {
|
||||
pub async fn check_captcha(
|
||||
pool: &mut DbPool<'_>,
|
||||
to_check: CheckCaptchaAnswer,
|
||||
) -> Result<bool, Error> {
|
||||
) -> LemmyResult<()> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
|
||||
// fetch requested captcha
|
||||
@ -43,13 +44,13 @@ impl CaptchaAnswer {
|
||||
.execute(conn)
|
||||
.await?;
|
||||
|
||||
Ok(captcha_exists)
|
||||
captcha_exists
|
||||
.then_some(())
|
||||
.ok_or(LemmyErrorType::CaptchaIncorrect.into())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
#[allow(clippy::indexing_slicing)]
|
||||
mod tests {
|
||||
|
||||
use crate::{
|
||||
@ -83,7 +84,6 @@ mod tests {
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert!(result.unwrap());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@ -119,7 +119,6 @@ mod tests {
|
||||
)
|
||||
.await;
|
||||
|
||||
assert!(result_repeat.is_ok());
|
||||
assert!(!result_repeat.unwrap());
|
||||
assert!(result_repeat.is_err());
|
||||
}
|
||||
}
|
||||
|
@ -196,8 +196,7 @@ impl Saveable for CommentSaved {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
#[allow(clippy::indexing_slicing)]
|
||||
#[expect(clippy::unwrap_used)]
|
||||
mod tests {
|
||||
|
||||
use crate::{
|
||||
|
@ -194,7 +194,7 @@ impl Community {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_random_local_community(pool: &mut DbPool<'_>) -> Result<Option<Self>, Error> {
|
||||
pub async fn get_random_local_community(pool: &mut DbPool<'_>) -> Result<Self, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
sql_function!(fn random() -> Text);
|
||||
community::table
|
||||
@ -205,7 +205,6 @@ impl Community {
|
||||
.limit(1)
|
||||
.first::<Self>(conn)
|
||||
.await
|
||||
.optional()
|
||||
}
|
||||
}
|
||||
|
||||
@ -333,16 +332,18 @@ impl CommunityFollower {
|
||||
|
||||
/// 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(
|
||||
pub async fn check_has_local_followers(
|
||||
pool: &mut DbPool<'_>,
|
||||
remote_community_id: CommunityId,
|
||||
) -> Result<bool, Error> {
|
||||
) -> LemmyResult<()> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
select(exists(community_follower::table.filter(
|
||||
community_follower::community_id.eq(remote_community_id),
|
||||
)))
|
||||
.get_result(conn)
|
||||
.await
|
||||
.get_result::<bool>(conn)
|
||||
.await?
|
||||
.then_some(())
|
||||
.ok_or(LemmyErrorType::CommunityHasNoFollowers.into())
|
||||
}
|
||||
}
|
||||
|
||||
@ -443,7 +444,6 @@ impl ApubActor for Community {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::indexing_slicing)]
|
||||
mod tests {
|
||||
use crate::{
|
||||
source::{
|
||||
|
@ -9,26 +9,29 @@ use crate::{
|
||||
utils::{get_conn, DbPool},
|
||||
};
|
||||
use diesel::{
|
||||
dsl::{exists, insert_into},
|
||||
dsl::{exists, insert_into, not},
|
||||
result::Error,
|
||||
select,
|
||||
ExpressionMethods,
|
||||
QueryDsl,
|
||||
};
|
||||
use diesel_async::RunQueryDsl;
|
||||
use lemmy_utils::{error::LemmyResult, LemmyErrorType};
|
||||
|
||||
impl CommunityBlock {
|
||||
pub async fn read(
|
||||
pool: &mut DbPool<'_>,
|
||||
for_person_id: PersonId,
|
||||
for_community_id: CommunityId,
|
||||
) -> Result<bool, Error> {
|
||||
) -> LemmyResult<()> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
select(exists(
|
||||
select(not(exists(
|
||||
community_block::table.find((for_person_id, for_community_id)),
|
||||
))
|
||||
.get_result(conn)
|
||||
.await
|
||||
)))
|
||||
.get_result::<bool>(conn)
|
||||
.await?
|
||||
.then_some(())
|
||||
.ok_or(LemmyErrorType::CommunityIsBlocked.into())
|
||||
}
|
||||
|
||||
pub async fn for_person(
|
||||
|
@ -48,8 +48,7 @@ impl FederationAllowList {
|
||||
}
|
||||
}
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
#[allow(clippy::indexing_slicing)]
|
||||
#[expect(clippy::unwrap_used)]
|
||||
mod tests {
|
||||
|
||||
use crate::{
|
||||
|
@ -9,26 +9,29 @@ use crate::{
|
||||
utils::{get_conn, DbPool},
|
||||
};
|
||||
use diesel::{
|
||||
dsl::{exists, insert_into},
|
||||
dsl::{exists, insert_into, not},
|
||||
result::Error,
|
||||
select,
|
||||
ExpressionMethods,
|
||||
QueryDsl,
|
||||
};
|
||||
use diesel_async::RunQueryDsl;
|
||||
use lemmy_utils::{error::LemmyResult, LemmyErrorType};
|
||||
|
||||
impl InstanceBlock {
|
||||
pub async fn read(
|
||||
pool: &mut DbPool<'_>,
|
||||
for_person_id: PersonId,
|
||||
for_instance_id: InstanceId,
|
||||
) -> Result<bool, Error> {
|
||||
) -> LemmyResult<()> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
select(exists(
|
||||
select(not(exists(
|
||||
instance_block::table.find((for_person_id, for_instance_id)),
|
||||
))
|
||||
.get_result(conn)
|
||||
.await
|
||||
)))
|
||||
.get_result::<bool>(conn)
|
||||
.await?
|
||||
.then_some(())
|
||||
.ok_or(LemmyErrorType::InstanceIsBlocked.into())
|
||||
}
|
||||
|
||||
pub async fn for_person(
|
||||
|
@ -41,8 +41,8 @@ impl Language {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
#[allow(clippy::indexing_slicing)]
|
||||
#[expect(clippy::unwrap_used)]
|
||||
#[expect(clippy::indexing_slicing)]
|
||||
mod tests {
|
||||
|
||||
use crate::{source::language::Language, utils::build_db_pool_for_tests};
|
||||
|
@ -136,14 +136,16 @@ impl LocalUser {
|
||||
diesel::delete(persons).execute(conn).await
|
||||
}
|
||||
|
||||
pub async fn is_email_taken(pool: &mut DbPool<'_>, email: &str) -> Result<bool, Error> {
|
||||
pub async fn check_is_email_taken(pool: &mut DbPool<'_>, email: &str) -> LemmyResult<()> {
|
||||
use diesel::dsl::{exists, select};
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
select(exists(local_user::table.filter(
|
||||
select(not(exists(local_user::table.filter(
|
||||
lower(coalesce(local_user::email, "")).eq(email.to_lowercase()),
|
||||
)))
|
||||
.get_result(conn)
|
||||
.await
|
||||
))))
|
||||
.get_result::<bool>(conn)
|
||||
.await?
|
||||
.then_some(())
|
||||
.ok_or(LemmyErrorType::EmailAlreadyExists.into())
|
||||
}
|
||||
|
||||
// TODO: maybe move this and pass in LocalUserView
|
||||
@ -367,7 +369,6 @@ pub struct UserBackupLists {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::indexing_slicing)]
|
||||
mod tests {
|
||||
use crate::{
|
||||
source::{
|
||||
@ -419,4 +420,32 @@ mod tests {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_email_taken() -> LemmyResult<()> {
|
||||
let pool = &build_db_pool_for_tests().await;
|
||||
let pool = &mut pool.into();
|
||||
|
||||
let darwin_email = "charles.darwin@gmail.com";
|
||||
|
||||
let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?;
|
||||
|
||||
let darwin_person = PersonInsertForm::test_form(inserted_instance.id, "darwin");
|
||||
let inserted_darwin_person = Person::create(pool, &darwin_person).await?;
|
||||
|
||||
let mut darwin_local_user_form =
|
||||
LocalUserInsertForm::test_form_admin(inserted_darwin_person.id);
|
||||
darwin_local_user_form.email = Some(darwin_email.into());
|
||||
let _inserted_darwin_local_user =
|
||||
LocalUser::create(pool, &darwin_local_user_form, vec![]).await?;
|
||||
|
||||
let check = LocalUser::check_is_email_taken(pool, darwin_email).await;
|
||||
assert!(check.is_err());
|
||||
|
||||
let passed_check = LocalUser::check_is_email_taken(pool, "not_charles@gmail.com").await;
|
||||
assert!(passed_check.is_ok());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ use crate::{
|
||||
};
|
||||
use diesel::{delete, dsl::exists, insert_into, result::Error, select};
|
||||
use diesel_async::RunQueryDsl;
|
||||
use lemmy_utils::{error::LemmyResult, LemmyErrorType};
|
||||
|
||||
impl LoginToken {
|
||||
pub async fn create(pool: &mut DbPool<'_>, form: LoginTokenCreateForm) -> Result<Self, Error> {
|
||||
@ -22,13 +23,15 @@ impl LoginToken {
|
||||
pool: &mut DbPool<'_>,
|
||||
user_id_: LocalUserId,
|
||||
token_: &str,
|
||||
) -> Result<bool, Error> {
|
||||
) -> LemmyResult<()> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
select(exists(
|
||||
login_token.find(token_).filter(user_id.eq(user_id_)),
|
||||
))
|
||||
.get_result(conn)
|
||||
.await
|
||||
.get_result::<bool>(conn)
|
||||
.await?
|
||||
.then_some(())
|
||||
.ok_or(LemmyErrorType::NotLoggedIn.into())
|
||||
}
|
||||
|
||||
pub async fn list(
|
||||
|
@ -465,8 +465,7 @@ impl Crud for AdminPurgeComment {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
#[allow(clippy::indexing_slicing)]
|
||||
#[expect(clippy::unwrap_used)]
|
||||
mod tests {
|
||||
|
||||
use crate::{
|
||||
|
@ -1,32 +1,13 @@
|
||||
use crate::{
|
||||
newtypes::{LocalUserId, OAuthProviderId},
|
||||
newtypes::LocalUserId,
|
||||
schema::{oauth_account, oauth_account::dsl::local_user_id},
|
||||
source::oauth_account::{OAuthAccount, OAuthAccountInsertForm},
|
||||
utils::{get_conn, DbPool},
|
||||
};
|
||||
use diesel::{
|
||||
dsl::{exists, insert_into},
|
||||
result::Error,
|
||||
select,
|
||||
ExpressionMethods,
|
||||
QueryDsl,
|
||||
};
|
||||
use diesel::{insert_into, result::Error, ExpressionMethods, QueryDsl};
|
||||
use diesel_async::RunQueryDsl;
|
||||
|
||||
impl OAuthAccount {
|
||||
pub async fn read(
|
||||
pool: &mut DbPool<'_>,
|
||||
for_oauth_provider_id: OAuthProviderId,
|
||||
for_local_user_id: LocalUserId,
|
||||
) -> Result<bool, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
select(exists(
|
||||
oauth_account::table.find((for_oauth_provider_id, for_local_user_id)),
|
||||
))
|
||||
.get_result(conn)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn create(pool: &mut DbPool<'_>, form: &OAuthAccountInsertForm) -> Result<Self, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
insert_into(oauth_account::table)
|
||||
@ -35,17 +16,6 @@ impl OAuthAccount {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn delete(
|
||||
pool: &mut DbPool<'_>,
|
||||
for_oauth_provider_id: OAuthProviderId,
|
||||
for_local_user_id: LocalUserId,
|
||||
) -> Result<usize, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
diesel::delete(oauth_account::table.find((for_oauth_provider_id, for_local_user_id)))
|
||||
.execute(conn)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn delete_user_accounts(
|
||||
pool: &mut DbPool<'_>,
|
||||
for_local_user_id: LocalUserId,
|
||||
|
@ -42,8 +42,6 @@ impl PasswordResetRequest {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
#[allow(clippy::indexing_slicing)]
|
||||
mod tests {
|
||||
|
||||
use crate::{
|
||||
|
@ -21,6 +21,7 @@ use diesel::{
|
||||
QueryDsl,
|
||||
};
|
||||
use diesel_async::RunQueryDsl;
|
||||
use lemmy_utils::{error::LemmyResult, LemmyErrorType};
|
||||
|
||||
#[async_trait]
|
||||
impl Crud for Person {
|
||||
@ -121,16 +122,18 @@ impl Person {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn is_username_taken(pool: &mut DbPool<'_>, username: &str) -> Result<bool, Error> {
|
||||
pub async fn check_username_taken(pool: &mut DbPool<'_>, username: &str) -> LemmyResult<()> {
|
||||
use diesel::dsl::{exists, select};
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
select(exists(
|
||||
select(not(exists(
|
||||
person::table
|
||||
.filter(lower(person::name).eq(username.to_lowercase()))
|
||||
.filter(person::local.eq(true)),
|
||||
))
|
||||
.get_result(conn)
|
||||
.await
|
||||
)))
|
||||
.get_result::<bool>(conn)
|
||||
.await?
|
||||
.then_some(())
|
||||
.ok_or(LemmyErrorType::UsernameAlreadyExists.into())
|
||||
}
|
||||
}
|
||||
|
||||
@ -232,7 +235,6 @@ impl PersonFollower {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::indexing_slicing)]
|
||||
mod tests {
|
||||
|
||||
use crate::{
|
||||
|
@ -9,7 +9,7 @@ use crate::{
|
||||
utils::{get_conn, DbPool},
|
||||
};
|
||||
use diesel::{
|
||||
dsl::{exists, insert_into},
|
||||
dsl::{exists, insert_into, not},
|
||||
result::Error,
|
||||
select,
|
||||
ExpressionMethods,
|
||||
@ -17,19 +17,22 @@ use diesel::{
|
||||
QueryDsl,
|
||||
};
|
||||
use diesel_async::RunQueryDsl;
|
||||
use lemmy_utils::{error::LemmyResult, LemmyErrorType};
|
||||
|
||||
impl PersonBlock {
|
||||
pub async fn read(
|
||||
pool: &mut DbPool<'_>,
|
||||
for_person_id: PersonId,
|
||||
for_recipient_id: PersonId,
|
||||
) -> Result<bool, Error> {
|
||||
) -> LemmyResult<()> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
select(exists(
|
||||
select(not(exists(
|
||||
person_block::table.find((for_person_id, for_recipient_id)),
|
||||
))
|
||||
.get_result(conn)
|
||||
.await
|
||||
)))
|
||||
.get_result::<bool>(conn)
|
||||
.await?
|
||||
.then_some(())
|
||||
.ok_or(LemmyErrorType::PersonIsBlocked.into())
|
||||
}
|
||||
|
||||
pub async fn for_person(
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
diesel::OptionalExtension,
|
||||
diesel::{BoolExpressionMethods, OptionalExtension},
|
||||
newtypes::{CommunityId, DbUrl, PersonId, PostId},
|
||||
schema::{post, post_hide, post_like, post_read, post_saved},
|
||||
schema::{community, person, post, post_hide, post_like, post_read, post_saved},
|
||||
source::post::{
|
||||
Post,
|
||||
PostHide,
|
||||
@ -20,6 +20,7 @@ use crate::{
|
||||
functions::coalesce,
|
||||
get_conn,
|
||||
naive_now,
|
||||
now,
|
||||
DbPool,
|
||||
DELETED_REPLACEMENT_TEXT,
|
||||
FETCH_LIMIT_MAX,
|
||||
@ -30,7 +31,7 @@ use crate::{
|
||||
use ::url::Url;
|
||||
use chrono::{DateTime, Utc};
|
||||
use diesel::{
|
||||
dsl::insert_into,
|
||||
dsl::{count, insert_into, not},
|
||||
result::Error,
|
||||
DecoratableTarget,
|
||||
ExpressionMethods,
|
||||
@ -173,6 +174,7 @@ impl Post {
|
||||
let object_id: DbUrl = object_id.into();
|
||||
post::table
|
||||
.filter(post::ap_id.eq(object_id))
|
||||
.filter(post::scheduled_publish_time.is_null())
|
||||
.first(conn)
|
||||
.await
|
||||
.optional()
|
||||
@ -246,6 +248,28 @@ impl Post {
|
||||
.get_results::<Self>(conn)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn user_scheduled_post_count(
|
||||
person_id: PersonId,
|
||||
pool: &mut DbPool<'_>,
|
||||
) -> Result<i64, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
|
||||
post::table
|
||||
.inner_join(person::table)
|
||||
.inner_join(community::table)
|
||||
// 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()).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)))
|
||||
// only posts by specified user
|
||||
.filter(post::creator_id.eq(person_id))
|
||||
.select(count(post::id))
|
||||
.first::<i64>(conn)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@ -368,8 +392,7 @@ impl PostHide {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
#[allow(clippy::indexing_slicing)]
|
||||
#[expect(clippy::unwrap_used)]
|
||||
mod tests {
|
||||
|
||||
use crate::{
|
||||
@ -459,6 +482,7 @@ mod tests {
|
||||
featured_community: false,
|
||||
featured_local: false,
|
||||
url_content_type: None,
|
||||
scheduled_publish_time: None,
|
||||
};
|
||||
|
||||
// Post Like
|
||||
|
@ -80,8 +80,7 @@ impl Reportable for PostReport {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
#[allow(clippy::indexing_slicing)]
|
||||
#[expect(clippy::unwrap_used)]
|
||||
mod tests {
|
||||
|
||||
use super::*;
|
||||
|
@ -85,8 +85,7 @@ impl PrivateMessage {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
#[allow(clippy::indexing_slicing)]
|
||||
#[expect(clippy::unwrap_used)]
|
||||
mod tests {
|
||||
|
||||
use crate::{
|
||||
|
@ -5,7 +5,7 @@ use crate::{
|
||||
traits::Crud,
|
||||
utils::{get_conn, limit_and_offset, DbPool},
|
||||
};
|
||||
use diesel::{insert_into, result::Error, ExpressionMethods, OptionalExtension, QueryDsl};
|
||||
use diesel::{insert_into, result::Error, ExpressionMethods, QueryDsl};
|
||||
use diesel_async::RunQueryDsl;
|
||||
|
||||
#[async_trait]
|
||||
@ -51,14 +51,9 @@ impl Tagline {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_random(pool: &mut DbPool<'_>) -> Result<Option<Self>, Error> {
|
||||
pub async fn get_random(pool: &mut DbPool<'_>) -> Result<Self, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
sql_function!(fn random() -> Text);
|
||||
tagline
|
||||
.order(random())
|
||||
.limit(1)
|
||||
.first::<Self>(conn)
|
||||
.await
|
||||
.optional()
|
||||
tagline.order(random()).limit(1).first::<Self>(conn).await
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +27,6 @@ pub mod newtypes;
|
||||
pub mod sensitive;
|
||||
#[cfg(feature = "full")]
|
||||
#[rustfmt::skip]
|
||||
#[allow(clippy::wildcard_imports)]
|
||||
pub mod schema;
|
||||
#[cfg(feature = "full")]
|
||||
pub mod aliases {
|
||||
|
@ -191,13 +191,13 @@ impl Display for DbUrl {
|
||||
}
|
||||
|
||||
// the project doesn't compile with From
|
||||
#[allow(clippy::from_over_into)]
|
||||
#[expect(clippy::from_over_into)]
|
||||
impl Into<DbUrl> for Url {
|
||||
fn into(self) -> DbUrl {
|
||||
DbUrl(Box::new(self))
|
||||
}
|
||||
}
|
||||
#[allow(clippy::from_over_into)]
|
||||
#[expect(clippy::from_over_into)]
|
||||
impl Into<Url> for DbUrl {
|
||||
fn into(self) -> Url {
|
||||
*self.0
|
||||
|
@ -459,7 +459,6 @@ diesel::table! {
|
||||
totp_2fa_secret -> Nullable<Text>,
|
||||
open_links_in_new_tab -> Bool,
|
||||
blur_nsfw -> Bool,
|
||||
auto_expand -> Bool,
|
||||
infinite_scroll_enabled -> Bool,
|
||||
admin -> Bool,
|
||||
post_listing_mode -> PostListingModeEnum,
|
||||
@ -770,6 +769,7 @@ diesel::table! {
|
||||
featured_local -> Bool,
|
||||
url_content_type -> Nullable<Text>,
|
||||
alt_text -> Nullable<Text>,
|
||||
scheduled_publish_time -> Nullable<Timestamptz>
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -49,7 +49,6 @@ pub struct LocalUser {
|
||||
/// Open links in a new tab.
|
||||
pub open_links_in_new_tab: bool,
|
||||
pub blur_nsfw: bool,
|
||||
pub auto_expand: bool,
|
||||
/// Whether infinite scroll is enabled.
|
||||
pub infinite_scroll_enabled: bool,
|
||||
/// Whether the person is an admin.
|
||||
@ -104,8 +103,6 @@ pub struct LocalUserInsertForm {
|
||||
#[new(default)]
|
||||
pub blur_nsfw: Option<bool>,
|
||||
#[new(default)]
|
||||
pub auto_expand: Option<bool>,
|
||||
#[new(default)]
|
||||
pub infinite_scroll_enabled: Option<bool>,
|
||||
#[new(default)]
|
||||
pub admin: Option<bool>,
|
||||
@ -143,7 +140,6 @@ pub struct LocalUserUpdateForm {
|
||||
pub totp_2fa_secret: Option<Option<String>>,
|
||||
pub open_links_in_new_tab: Option<bool>,
|
||||
pub blur_nsfw: Option<bool>,
|
||||
pub auto_expand: Option<bool>,
|
||||
pub infinite_scroll_enabled: Option<bool>,
|
||||
pub admin: Option<bool>,
|
||||
pub post_listing_mode: Option<PostListingMode>,
|
||||
|
@ -57,6 +57,8 @@ pub struct Post {
|
||||
pub url_content_type: Option<String>,
|
||||
/// An optional alt_text, usable for image posts.
|
||||
pub alt_text: Option<String>,
|
||||
/// Time at which the post will be published. None means publish immediately.
|
||||
pub scheduled_publish_time: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, derive_new::new)]
|
||||
@ -104,6 +106,8 @@ pub struct PostInsertForm {
|
||||
pub url_content_type: Option<String>,
|
||||
#[new(default)]
|
||||
pub alt_text: Option<String>,
|
||||
#[new(default)]
|
||||
pub scheduled_publish_time: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
@ -130,6 +134,7 @@ pub struct PostUpdateForm {
|
||||
pub featured_local: Option<bool>,
|
||||
pub url_content_type: Option<Option<String>>,
|
||||
pub alt_text: Option<Option<String>>,
|
||||
pub scheduled_publish_time: Option<Option<DateTime<Utc>>>,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug)]
|
||||
|
@ -595,7 +595,6 @@ impl<RF, LF> Queries<RF, LF> {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::indexing_slicing)]
|
||||
mod tests {
|
||||
|
||||
use super::*;
|
||||
|
@ -259,8 +259,8 @@ impl CommentReportQuery {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
#[allow(clippy::indexing_slicing)]
|
||||
#[expect(clippy::unwrap_used)]
|
||||
#[expect(clippy::indexing_slicing)]
|
||||
mod tests {
|
||||
|
||||
use crate::{
|
||||
|
@ -35,7 +35,7 @@ use lemmy_db_schema::{
|
||||
person_block,
|
||||
post,
|
||||
},
|
||||
source::local_user::LocalUser,
|
||||
source::{local_user::LocalUser, site::Site},
|
||||
utils::{fuzzy_search, limit_and_offset, DbConn, DbPool, ListFn, Queries, ReadFn},
|
||||
CommentSortType,
|
||||
ListingType,
|
||||
@ -43,7 +43,7 @@ use lemmy_db_schema::{
|
||||
|
||||
fn queries<'a>() -> Queries<
|
||||
impl ReadFn<'a, CommentView, (CommentId, Option<&'a LocalUser>)>,
|
||||
impl ListFn<'a, CommentView, CommentQuery<'a>>,
|
||||
impl ListFn<'a, CommentView, (CommentQuery<'a>, &'a Site)>,
|
||||
> {
|
||||
let is_creator_banned_from_community = exists(
|
||||
community_person_ban::table.filter(
|
||||
@ -182,7 +182,7 @@ fn queries<'a>() -> Queries<
|
||||
query.first(&mut conn).await
|
||||
};
|
||||
|
||||
let list = move |mut conn: DbConn<'a>, options: CommentQuery<'a>| async move {
|
||||
let list = move |mut conn: DbConn<'a>, (options, site): (CommentQuery<'a>, &'a Site)| async move {
|
||||
// The left join below will return None in this case
|
||||
let person_id_join = options.local_user.person_id().unwrap_or(PersonId(-1));
|
||||
let local_user_id_join = options
|
||||
@ -295,6 +295,12 @@ fn queries<'a>() -> Queries<
|
||||
query = query.filter(not(is_creator_blocked(person_id_join)));
|
||||
};
|
||||
|
||||
if !options.local_user.show_nsfw(site) {
|
||||
query = query
|
||||
.filter(post::nsfw.eq(false))
|
||||
.filter(community::nsfw.eq(false));
|
||||
};
|
||||
|
||||
query = options.local_user.visible_communities_only(query);
|
||||
|
||||
// A Max depth given means its a tree fetch
|
||||
@ -398,10 +404,10 @@ pub struct CommentQuery<'a> {
|
||||
}
|
||||
|
||||
impl<'a> CommentQuery<'a> {
|
||||
pub async fn list(self, pool: &mut DbPool<'_>) -> Result<Vec<CommentView>, Error> {
|
||||
pub async fn list(self, site: &Site, pool: &mut DbPool<'_>) -> Result<Vec<CommentView>, Error> {
|
||||
Ok(
|
||||
queries()
|
||||
.list(pool, self)
|
||||
.list(pool, (self, site))
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|mut c| {
|
||||
@ -416,8 +422,8 @@ impl<'a> CommentQuery<'a> {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::indexing_slicing)]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
#[expect(clippy::indexing_slicing)]
|
||||
#[expect(clippy::unwrap_used)]
|
||||
mod tests {
|
||||
|
||||
use crate::{
|
||||
@ -455,7 +461,8 @@ mod tests {
|
||||
local_user_vote_display_mode::LocalUserVoteDisplayMode,
|
||||
person::{Person, PersonInsertForm},
|
||||
person_block::{PersonBlock, PersonBlockForm},
|
||||
post::{Post, PostInsertForm},
|
||||
post::{Post, PostInsertForm, PostUpdateForm},
|
||||
site::{Site, SiteInsertForm},
|
||||
},
|
||||
traits::{Bannable, Blockable, Crud, Joinable, Likeable, Saveable},
|
||||
utils::{build_db_pool_for_tests, RANK_DEFAULT},
|
||||
@ -475,6 +482,7 @@ mod tests {
|
||||
timmy_local_user_view: LocalUserView,
|
||||
inserted_sara_person: Person,
|
||||
inserted_community: Community,
|
||||
site: Site,
|
||||
}
|
||||
|
||||
async fn init_data(pool: &mut DbPool<'_>) -> LemmyResult<Data> {
|
||||
@ -611,6 +619,8 @@ mod tests {
|
||||
person: inserted_timmy_person.clone(),
|
||||
counts: Default::default(),
|
||||
};
|
||||
let site_form = SiteInsertForm::new("test site".to_string(), inserted_instance.id);
|
||||
let site = Site::create(pool, &site_form).await?;
|
||||
Ok(Data {
|
||||
inserted_instance,
|
||||
inserted_comment_0,
|
||||
@ -620,6 +630,7 @@ mod tests {
|
||||
timmy_local_user_view,
|
||||
inserted_sara_person,
|
||||
inserted_community,
|
||||
site,
|
||||
})
|
||||
}
|
||||
|
||||
@ -640,7 +651,7 @@ mod tests {
|
||||
post_id: (Some(data.inserted_post.id)),
|
||||
..Default::default()
|
||||
}
|
||||
.list(pool)
|
||||
.list(&data.site, pool)
|
||||
.await?;
|
||||
|
||||
assert_eq!(
|
||||
@ -654,7 +665,7 @@ mod tests {
|
||||
local_user: (Some(&data.timmy_local_user_view.local_user)),
|
||||
..Default::default()
|
||||
}
|
||||
.list(pool)
|
||||
.list(&data.site, pool)
|
||||
.await?;
|
||||
|
||||
assert_eq!(
|
||||
@ -706,7 +717,7 @@ mod tests {
|
||||
liked_only: Some(true),
|
||||
..Default::default()
|
||||
}
|
||||
.list(pool)
|
||||
.list(&data.site, pool)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|c| c.comment.content)
|
||||
@ -722,7 +733,7 @@ mod tests {
|
||||
disliked_only: Some(true),
|
||||
..Default::default()
|
||||
}
|
||||
.list(pool)
|
||||
.list(&data.site, pool)
|
||||
.await?;
|
||||
|
||||
assert!(read_disliked_comment_views.is_empty());
|
||||
@ -743,7 +754,7 @@ mod tests {
|
||||
parent_path: (Some(top_path)),
|
||||
..Default::default()
|
||||
}
|
||||
.list(pool)
|
||||
.list(&data.site, pool)
|
||||
.await?;
|
||||
|
||||
let child_path = data.inserted_comment_1.path.clone();
|
||||
@ -752,7 +763,7 @@ mod tests {
|
||||
parent_path: (Some(child_path)),
|
||||
..Default::default()
|
||||
}
|
||||
.list(pool)
|
||||
.list(&data.site, pool)
|
||||
.await?;
|
||||
|
||||
// Make sure the comment parent-limited fetch is correct
|
||||
@ -772,7 +783,7 @@ mod tests {
|
||||
max_depth: (Some(1)),
|
||||
..Default::default()
|
||||
}
|
||||
.list(pool)
|
||||
.list(&data.site, pool)
|
||||
.await?;
|
||||
|
||||
// Make sure a depth limited one only has the top comment
|
||||
@ -790,7 +801,7 @@ mod tests {
|
||||
sort: (Some(CommentSortType::New)),
|
||||
..Default::default()
|
||||
}
|
||||
.list(pool)
|
||||
.list(&data.site, pool)
|
||||
.await?;
|
||||
|
||||
// Make sure a depth limited one, and given child comment 1, has 3
|
||||
@ -816,7 +827,7 @@ mod tests {
|
||||
local_user: (Some(&data.timmy_local_user_view.local_user)),
|
||||
..Default::default()
|
||||
}
|
||||
.list(pool)
|
||||
.list(&data.site, pool)
|
||||
.await?;
|
||||
assert_length!(5, all_languages);
|
||||
|
||||
@ -834,7 +845,7 @@ mod tests {
|
||||
local_user: (Some(&data.timmy_local_user_view.local_user)),
|
||||
..Default::default()
|
||||
}
|
||||
.list(pool)
|
||||
.list(&data.site, pool)
|
||||
.await?;
|
||||
assert_length!(2, finnish_comments);
|
||||
let finnish_comment = finnish_comments
|
||||
@ -857,7 +868,7 @@ mod tests {
|
||||
local_user: (Some(&data.timmy_local_user_view.local_user)),
|
||||
..Default::default()
|
||||
}
|
||||
.list(pool)
|
||||
.list(&data.site, pool)
|
||||
.await?;
|
||||
assert_length!(1, undetermined_comment);
|
||||
|
||||
@ -881,7 +892,7 @@ mod tests {
|
||||
post_id: Some(data.inserted_comment_2.post_id),
|
||||
..Default::default()
|
||||
}
|
||||
.list(pool)
|
||||
.list(&data.site, pool)
|
||||
.await?;
|
||||
assert_eq!(comments[0].comment.id, data.inserted_comment_2.id);
|
||||
assert!(comments[0].comment.distinguished);
|
||||
@ -910,7 +921,7 @@ mod tests {
|
||||
sort: (Some(CommentSortType::Old)),
|
||||
..Default::default()
|
||||
}
|
||||
.list(pool)
|
||||
.list(&data.site, pool)
|
||||
.await?;
|
||||
|
||||
assert_eq!(comments[1].creator.name, "sara");
|
||||
@ -931,7 +942,7 @@ mod tests {
|
||||
sort: (Some(CommentSortType::Old)),
|
||||
..Default::default()
|
||||
}
|
||||
.list(pool)
|
||||
.list(&data.site, pool)
|
||||
.await?;
|
||||
|
||||
// Timmy is an admin, and make sure that field is true
|
||||
@ -971,7 +982,7 @@ mod tests {
|
||||
saved_only: Some(true),
|
||||
..Default::default()
|
||||
}
|
||||
.list(pool)
|
||||
.list(&data.site, pool)
|
||||
.await?;
|
||||
|
||||
// There should only be two comments
|
||||
@ -1001,6 +1012,7 @@ mod tests {
|
||||
LocalUser::delete(pool, data.timmy_local_user_view.local_user.id).await?;
|
||||
Person::delete(pool, data.inserted_sara_person.id).await?;
|
||||
Instance::delete(pool, data.inserted_instance.id).await?;
|
||||
Site::delete(pool, data.site.id).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -1078,6 +1090,7 @@ mod tests {
|
||||
featured_community: false,
|
||||
featured_local: false,
|
||||
url_content_type: None,
|
||||
scheduled_publish_time: None,
|
||||
},
|
||||
community: Community {
|
||||
id: data.inserted_community.id,
|
||||
@ -1139,7 +1152,7 @@ mod tests {
|
||||
let unauthenticated_query = CommentQuery {
|
||||
..Default::default()
|
||||
}
|
||||
.list(pool)
|
||||
.list(&data.site, pool)
|
||||
.await?;
|
||||
assert_eq!(0, unauthenticated_query.len());
|
||||
|
||||
@ -1147,7 +1160,7 @@ mod tests {
|
||||
local_user: Some(&data.timmy_local_user_view.local_user),
|
||||
..Default::default()
|
||||
}
|
||||
.list(pool)
|
||||
.list(&data.site, pool)
|
||||
.await?;
|
||||
assert_eq!(5, authenticated_query.len());
|
||||
|
||||
@ -1225,4 +1238,33 @@ mod tests {
|
||||
|
||||
cleanup(data, pool).await
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn comment_listings_hide_nsfw() -> LemmyResult<()> {
|
||||
let pool = &build_db_pool_for_tests().await;
|
||||
let pool = &mut pool.into();
|
||||
let data = init_data(pool).await?;
|
||||
|
||||
// Mark a post as nsfw
|
||||
let update_form = PostUpdateForm {
|
||||
nsfw: Some(true),
|
||||
..Default::default()
|
||||
};
|
||||
Post::update(pool, data.inserted_post.id, &update_form).await?;
|
||||
|
||||
// Make sure comments of this post are not returned
|
||||
let comments = CommentQuery::default().list(&data.site, pool).await?;
|
||||
assert_eq!(0, comments.len());
|
||||
|
||||
// Mark site as nsfw
|
||||
let mut site = data.site.clone();
|
||||
site.content_warning = Some("nsfw".to_string());
|
||||
|
||||
// Now comments of nsfw post are returned
|
||||
let comments = CommentQuery::default().list(&site, pool).await?;
|
||||
assert_eq!(6, comments.len());
|
||||
|
||||
cleanup(data, pool).await
|
||||
}
|
||||
}
|
||||
|
@ -284,8 +284,8 @@ impl PostReportQuery {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
#[allow(clippy::indexing_slicing)]
|
||||
#[expect(clippy::unwrap_used)]
|
||||
#[expect(clippy::indexing_slicing)]
|
||||
mod tests {
|
||||
|
||||
use crate::{
|
||||
|
@ -318,11 +318,18 @@ fn queries<'a>() -> Queries<
|
||||
// hide posts from deleted communities
|
||||
query = query.filter(community::deleted.eq(false));
|
||||
|
||||
// only show deleted posts to creator
|
||||
// only creator can see deleted posts and unpublished scheduled posts
|
||||
if let Some(person_id) = options.local_user.person_id() {
|
||||
query = query.filter(post::deleted.eq(false).or(post::creator_id.eq(person_id)));
|
||||
query = query.filter(
|
||||
post::scheduled_publish_time
|
||||
.is_null()
|
||||
.or(post::creator_id.eq(person_id)),
|
||||
);
|
||||
} else {
|
||||
query = query.filter(post::deleted.eq(false));
|
||||
query = query
|
||||
.filter(post::deleted.eq(false))
|
||||
.filter(post::scheduled_publish_time.is_null());
|
||||
}
|
||||
|
||||
// only show removed posts to admin when viewing user profile
|
||||
@ -387,14 +394,12 @@ fn queries<'a>() -> Queries<
|
||||
query = query.filter(post::url.eq(search_term));
|
||||
} else {
|
||||
let searcher = fuzzy_search(search_term);
|
||||
let name_filter = post::name.ilike(searcher.clone());
|
||||
let body_filter = post::body.ilike(searcher.clone());
|
||||
query = if options.title_only.unwrap_or_default() {
|
||||
query.filter(post::name.ilike(searcher))
|
||||
query.filter(name_filter)
|
||||
} else {
|
||||
query.filter(
|
||||
post::name
|
||||
.ilike(searcher.clone())
|
||||
.or(post::body.ilike(searcher)),
|
||||
)
|
||||
query.filter(name_filter.or(body_filter))
|
||||
}
|
||||
.filter(not(post::removed.or(post::deleted)));
|
||||
}
|
||||
@ -734,7 +739,7 @@ impl<'a> PostQuery<'a> {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
#[expect(clippy::unwrap_used)]
|
||||
mod tests {
|
||||
use crate::{
|
||||
post_view::{PaginationCursorData, PostQuery, PostView},
|
||||
@ -1771,6 +1776,7 @@ mod tests {
|
||||
featured_community: false,
|
||||
featured_local: false,
|
||||
url_content_type: None,
|
||||
scheduled_publish_time: None,
|
||||
},
|
||||
my_vote: None,
|
||||
unread_comments: 0,
|
||||
|
@ -111,8 +111,8 @@ impl PrivateMessageReportQuery {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
#[allow(clippy::indexing_slicing)]
|
||||
#[expect(clippy::unwrap_used)]
|
||||
#[expect(clippy::indexing_slicing)]
|
||||
mod tests {
|
||||
|
||||
use crate::private_message_report_view::PrivateMessageReportQuery;
|
||||
|
@ -173,8 +173,8 @@ impl PrivateMessageQuery {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
#[allow(clippy::indexing_slicing)]
|
||||
#[expect(clippy::unwrap_used)]
|
||||
#[expect(clippy::indexing_slicing)]
|
||||
mod tests {
|
||||
|
||||
use crate::{private_message_view::PrivateMessageQuery, structs::PrivateMessageView};
|
||||
|
@ -135,8 +135,7 @@ impl RegistrationApplicationQuery {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
#[allow(clippy::indexing_slicing)]
|
||||
#[expect(clippy::unwrap_used)]
|
||||
mod tests {
|
||||
|
||||
use crate::registration_application_view::{
|
||||
@ -235,7 +234,6 @@ mod tests {
|
||||
person_id: inserted_sara_local_user.person_id,
|
||||
email: inserted_sara_local_user.email,
|
||||
show_nsfw: inserted_sara_local_user.show_nsfw,
|
||||
auto_expand: inserted_sara_local_user.auto_expand,
|
||||
blur_nsfw: inserted_sara_local_user.blur_nsfw,
|
||||
theme: inserted_sara_local_user.theme,
|
||||
default_post_sort_type: inserted_sara_local_user.default_post_sort_type,
|
||||
|
@ -83,8 +83,7 @@ impl VoteView {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
#[allow(clippy::indexing_slicing)]
|
||||
#[expect(clippy::unwrap_used)]
|
||||
mod tests {
|
||||
|
||||
use crate::structs::VoteView;
|
||||
|
@ -15,7 +15,13 @@ doctest = false
|
||||
workspace = true
|
||||
|
||||
[features]
|
||||
full = ["lemmy_db_schema/full", "diesel", "diesel-async", "ts-rs"]
|
||||
full = [
|
||||
"lemmy_db_schema/full",
|
||||
"lemmy_utils/full",
|
||||
"diesel",
|
||||
"diesel-async",
|
||||
"ts-rs",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
lemmy_db_schema = { workspace = true }
|
||||
@ -33,6 +39,7 @@ serde_with = { workspace = true }
|
||||
ts-rs = { workspace = true, optional = true }
|
||||
chrono.workspace = true
|
||||
strum = { workspace = true }
|
||||
lemmy_utils = { workspace = true, optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
serial_test = { workspace = true }
|
||||
|
@ -303,7 +303,6 @@ impl CommentReplyQuery {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::indexing_slicing)]
|
||||
mod tests {
|
||||
|
||||
use crate::{comment_reply_view::CommentReplyQuery, structs::CommentReplyView};
|
||||
|
@ -8,13 +8,14 @@ use lemmy_db_schema::{
|
||||
source::local_user::LocalUser,
|
||||
utils::{get_conn, DbPool},
|
||||
};
|
||||
use lemmy_utils::{error::LemmyResult, LemmyErrorType};
|
||||
|
||||
impl CommunityModeratorView {
|
||||
pub async fn is_community_moderator(
|
||||
pub async fn check_is_community_moderator(
|
||||
pool: &mut DbPool<'_>,
|
||||
find_community_id: CommunityId,
|
||||
find_person_id: PersonId,
|
||||
) -> Result<bool, Error> {
|
||||
) -> LemmyResult<()> {
|
||||
use lemmy_db_schema::schema::community_moderator::dsl::{
|
||||
community_id,
|
||||
community_moderator,
|
||||
@ -27,20 +28,24 @@ impl CommunityModeratorView {
|
||||
.filter(person_id.eq(find_person_id)),
|
||||
))
|
||||
.get_result::<bool>(conn)
|
||||
.await
|
||||
.await?
|
||||
.then_some(())
|
||||
.ok_or(LemmyErrorType::NotAModerator.into())
|
||||
}
|
||||
|
||||
pub(crate) async fn is_community_moderator_of_any(
|
||||
pool: &mut DbPool<'_>,
|
||||
find_person_id: PersonId,
|
||||
) -> Result<bool, Error> {
|
||||
) -> LemmyResult<()> {
|
||||
use lemmy_db_schema::schema::community_moderator::dsl::{community_moderator, person_id};
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
select(exists(
|
||||
community_moderator.filter(person_id.eq(find_person_id)),
|
||||
))
|
||||
.get_result::<bool>(conn)
|
||||
.await
|
||||
.await?
|
||||
.then_some(())
|
||||
.ok_or(LemmyErrorType::NotAModerator.into())
|
||||
}
|
||||
|
||||
pub async fn for_community(
|
||||
|
@ -1,25 +1,33 @@
|
||||
use crate::structs::CommunityPersonBanView;
|
||||
use diesel::{dsl::exists, result::Error, select, ExpressionMethods, QueryDsl};
|
||||
use diesel::{
|
||||
dsl::{exists, not},
|
||||
select,
|
||||
ExpressionMethods,
|
||||
QueryDsl,
|
||||
};
|
||||
use diesel_async::RunQueryDsl;
|
||||
use lemmy_db_schema::{
|
||||
newtypes::{CommunityId, PersonId},
|
||||
schema::community_person_ban,
|
||||
utils::{get_conn, DbPool},
|
||||
};
|
||||
use lemmy_utils::{error::LemmyResult, LemmyErrorType};
|
||||
|
||||
impl CommunityPersonBanView {
|
||||
pub async fn get(
|
||||
pub async fn check(
|
||||
pool: &mut DbPool<'_>,
|
||||
from_person_id: PersonId,
|
||||
from_community_id: CommunityId,
|
||||
) -> Result<bool, Error> {
|
||||
) -> LemmyResult<()> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
select(exists(
|
||||
select(not(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::<bool>(conn)
|
||||
.await
|
||||
.await?
|
||||
.then_some(())
|
||||
.ok_or(LemmyErrorType::PersonIsBannedFromCommunity.into())
|
||||
}
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ use lemmy_db_schema::{
|
||||
ListingType,
|
||||
PostSortType,
|
||||
};
|
||||
use lemmy_utils::{error::LemmyResult, LemmyErrorType};
|
||||
|
||||
fn queries<'a>() -> Queries<
|
||||
impl ReadFn<'a, CommunityView, (CommunityId, Option<&'a LocalUser>, bool)>,
|
||||
@ -111,9 +112,14 @@ fn queries<'a>() -> Queries<
|
||||
|
||||
if let Some(search_term) = options.search_term {
|
||||
let searcher = fuzzy_search(&search_term);
|
||||
query = query
|
||||
.filter(community::name.ilike(searcher.clone()))
|
||||
.or_filter(community::title.ilike(searcher))
|
||||
let name_filter = community::name.ilike(searcher.clone());
|
||||
let title_filter = community::title.ilike(searcher.clone());
|
||||
let description_filter = community::description.ilike(searcher.clone());
|
||||
query = if options.title_only.unwrap_or_default() {
|
||||
query.filter(name_filter.or(title_filter))
|
||||
} else {
|
||||
query.filter(name_filter.or(title_filter.or(description_filter)))
|
||||
}
|
||||
}
|
||||
|
||||
// Hide deleted and removed for non-admins or mods
|
||||
@ -185,35 +191,39 @@ impl CommunityView {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn is_mod_or_admin(
|
||||
pub async fn check_is_mod_or_admin(
|
||||
pool: &mut DbPool<'_>,
|
||||
person_id: PersonId,
|
||||
community_id: CommunityId,
|
||||
) -> Result<bool, Error> {
|
||||
) -> LemmyResult<()> {
|
||||
let is_mod =
|
||||
CommunityModeratorView::is_community_moderator(pool, community_id, person_id).await?;
|
||||
if is_mod {
|
||||
Ok(true)
|
||||
} else if let Ok(person_view) = PersonView::read(pool, person_id).await {
|
||||
Ok(person_view.is_admin)
|
||||
CommunityModeratorView::check_is_community_moderator(pool, community_id, person_id).await;
|
||||
if is_mod.is_ok()
|
||||
|| PersonView::read(pool, person_id)
|
||||
.await
|
||||
.is_ok_and(|t| t.is_admin)
|
||||
{
|
||||
Ok(())
|
||||
} else {
|
||||
Ok(false)
|
||||
Err(LemmyErrorType::NotAModOrAdmin)?
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if a person is an admin, or moderator of any community.
|
||||
pub async fn is_mod_of_any_or_admin(
|
||||
pub async fn check_is_mod_of_any_or_admin(
|
||||
pool: &mut DbPool<'_>,
|
||||
person_id: PersonId,
|
||||
) -> Result<bool, Error> {
|
||||
) -> LemmyResult<()> {
|
||||
let is_mod_of_any =
|
||||
CommunityModeratorView::is_community_moderator_of_any(pool, person_id).await?;
|
||||
if is_mod_of_any {
|
||||
Ok(true)
|
||||
} else if let Ok(person_view) = PersonView::read(pool, person_id).await {
|
||||
Ok(person_view.is_admin)
|
||||
CommunityModeratorView::is_community_moderator_of_any(pool, person_id).await;
|
||||
if is_mod_of_any.is_ok()
|
||||
|| PersonView::read(pool, person_id)
|
||||
.await
|
||||
.is_ok_and(|t| t.is_admin)
|
||||
{
|
||||
Ok(())
|
||||
} else {
|
||||
Ok(false)
|
||||
Err(LemmyErrorType::NotAModOrAdmin)?
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -224,6 +234,7 @@ pub struct CommunityQuery<'a> {
|
||||
pub sort: Option<PostSortType>,
|
||||
pub local_user: Option<&'a LocalUser>,
|
||||
pub search_term: Option<String>,
|
||||
pub title_only: Option<bool>,
|
||||
pub is_mod_or_admin: bool,
|
||||
pub show_nsfw: bool,
|
||||
pub page: Option<i64>,
|
||||
@ -237,8 +248,7 @@ impl<'a> CommunityQuery<'a> {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
#[allow(clippy::indexing_slicing)]
|
||||
#[expect(clippy::unwrap_used)]
|
||||
mod tests {
|
||||
|
||||
use crate::{community_view::CommunityQuery, structs::CommunityView};
|
||||
|
@ -303,7 +303,6 @@ impl PersonMentionQuery {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::indexing_slicing)]
|
||||
mod tests {
|
||||
|
||||
use crate::{person_mention_view::PersonMentionQuery, structs::PersonMentionView};
|
||||
|
@ -164,7 +164,7 @@ impl PersonQuery {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::indexing_slicing)]
|
||||
#[expect(clippy::indexing_slicing)]
|
||||
mod tests {
|
||||
|
||||
use super::*;
|
||||
|
@ -222,8 +222,8 @@ impl<T: DataSource> CommunityInboxCollector<T> {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
#[allow(clippy::indexing_slicing)]
|
||||
#[expect(clippy::unwrap_used)]
|
||||
#[expect(clippy::indexing_slicing)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use lemmy_db_schema::{
|
||||
|
@ -192,8 +192,8 @@ impl SendManager {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
#[allow(clippy::indexing_slicing)]
|
||||
#[expect(clippy::unwrap_used)]
|
||||
#[expect(clippy::indexing_slicing)]
|
||||
mod test {
|
||||
|
||||
use super::*;
|
||||
|
@ -439,8 +439,8 @@ impl InstanceWorker {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
#[allow(clippy::indexing_slicing)]
|
||||
#[expect(clippy::unwrap_used)]
|
||||
#[expect(clippy::indexing_slicing)]
|
||||
mod test {
|
||||
|
||||
use super::*;
|
||||
|
@ -172,6 +172,8 @@ pub enum LemmyErrorType {
|
||||
Unknown(String),
|
||||
CantDeleteSite,
|
||||
UrlLengthOverflow,
|
||||
PostScheduleTimeMustBeInFuture,
|
||||
TooManyScheduledPosts,
|
||||
NotFound,
|
||||
}
|
||||
|
||||
|
@ -221,8 +221,6 @@ fn parse_ip(addr: &str) -> Option<IpAddr> {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
#[allow(clippy::indexing_slicing)]
|
||||
mod tests {
|
||||
|
||||
#[test]
|
||||
|
@ -136,7 +136,6 @@ impl<K: Eq + Hash, C: MapLevel> MapLevel for Map<K, C> {
|
||||
.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(
|
||||
@ -161,7 +160,6 @@ impl<K: Eq + Hash, C: MapLevel> MapLevel for Map<K, C> {
|
||||
// Evaluated if `some_children_remaining` is false
|
||||
let total_has_refill_in_future = || {
|
||||
group.total.into_iter().any(|(action_type, bucket)| {
|
||||
#[allow(clippy::indexing_slicing)]
|
||||
let config = configs[action_type];
|
||||
bucket.update(now, config).tokens != config.capacity
|
||||
})
|
||||
@ -214,7 +212,6 @@ impl<C: Default> RateLimitedGroup<C> {
|
||||
now: InstantSecs,
|
||||
config: BucketConfig,
|
||||
) -> bool {
|
||||
#[allow(clippy::indexing_slicing)] // `EnumMap` has no `get` function
|
||||
let bucket = &mut self.total[action_type];
|
||||
|
||||
let new_bucket = bucket.update(now, config);
|
||||
@ -311,8 +308,7 @@ fn split_ipv6(ip: Ipv6Addr) -> ([u8; 6], u8, u8) {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
#[allow(clippy::indexing_slicing)]
|
||||
#[expect(clippy::unwrap_used)]
|
||||
mod tests {
|
||||
|
||||
use super::{ActionType, BucketConfig, InstantSecs, RateLimitState, RateLimitedGroup};
|
||||
@ -361,7 +357,6 @@ mod tests {
|
||||
assert!(post_passed);
|
||||
}
|
||||
|
||||
#[allow(clippy::indexing_slicing)]
|
||||
let expected_buckets = |factor: u32, tokens_consumed: u32| {
|
||||
let adjusted_configs = bucket_configs.map(|_, config| BucketConfig {
|
||||
capacity: config.capacity.saturating_mul(factor),
|
||||
|
@ -107,8 +107,7 @@ pub fn markdown_check_for_blocked_urls(text: &str, blocklist: &RegexSet) -> Lemm
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
#[allow(clippy::indexing_slicing)]
|
||||
#[expect(clippy::unwrap_used)]
|
||||
mod tests {
|
||||
|
||||
use super::*;
|
||||
|
@ -134,8 +134,6 @@ pub fn add(markdown_parser: &mut MarkdownIt) {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
#[allow(clippy::indexing_slicing)]
|
||||
mod tests {
|
||||
|
||||
use crate::utils::markdown::spoiler_rule::add;
|
||||
|
@ -34,8 +34,7 @@ pub fn scrape_text_for_mentions(text: &str) -> Vec<MentionData> {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
#[allow(clippy::indexing_slicing)]
|
||||
#[expect(clippy::indexing_slicing)]
|
||||
mod test {
|
||||
|
||||
use crate::utils::mention::scrape_text_for_mentions;
|
||||
|
@ -61,8 +61,7 @@ pub(crate) fn slurs_vec_to_str(slurs: &[&str]) -> String {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
#[allow(clippy::indexing_slicing)]
|
||||
#[expect(clippy::unwrap_used)]
|
||||
mod test {
|
||||
|
||||
use crate::utils::slurs::{remove_slurs, slur_check, slurs_vec_to_str};
|
||||
|
@ -351,7 +351,6 @@ pub fn build_url_str_without_scheme(url_str: &str) -> LemmyResult<String> {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::indexing_slicing)]
|
||||
mod tests {
|
||||
|
||||
use crate::{
|
||||
|
3
migrations/2024-09-16-095656_schedule-post/down.sql
Normal file
3
migrations/2024-09-16-095656_schedule-post/down.sql
Normal file
@ -0,0 +1,3 @@
|
||||
ALTER TABLE post
|
||||
DROP COLUMN scheduled_publish_time;
|
||||
|
5
migrations/2024-09-16-095656_schedule-post/up.sql
Normal file
5
migrations/2024-09-16-095656_schedule-post/up.sql
Normal file
@ -0,0 +1,5 @@
|
||||
ALTER TABLE post
|
||||
ADD COLUMN scheduled_publish_time timestamptz;
|
||||
|
||||
CREATE INDEX idx_post_scheduled_publish_time ON post (scheduled_publish_time);
|
||||
|
3
migrations/2024-09-23-133038_remove_auto_expand/down.sql
Normal file
3
migrations/2024-09-23-133038_remove_auto_expand/down.sql
Normal file
@ -0,0 +1,3 @@
|
||||
ALTER TABLE local_user
|
||||
ADD COLUMN auto_expand boolean NOT NULL DEFAULT FALSE;
|
||||
|
3
migrations/2024-09-23-133038_remove_auto_expand/up.sql
Normal file
3
migrations/2024-09-23-133038_remove_auto_expand/up.sql
Normal file
@ -0,0 +1,3 @@
|
||||
ALTER TABLE local_user
|
||||
DROP COLUMN auto_expand;
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user