Merge remote-tracking branch 'origin/main' into combined_profile

This commit is contained in:
Dessalines 2025-01-03 14:33:34 -05:00
commit 5cf0bb834a
14 changed files with 115 additions and 15 deletions

View file

@ -28,8 +28,10 @@ pub async fn list_person_saved(
None
};
let page_back = data.page_back;
let type_ = data.type_;
let saved = PersonSavedCombinedQuery {
type_,
page_after,
page_back,
}

View file

@ -19,6 +19,11 @@ pub async fn verify_email(
let site_view = SiteView::read_local(&mut context.pool()).await?;
let token = data.token.clone();
let verification = EmailVerification::read_for_token(&mut context.pool(), &token).await?;
let local_user_id = verification.local_user_id;
let local_user_view = LocalUserView::read(&mut context.pool(), local_user_id).await?;
// Check if their email has already been verified once, before this
let email_already_verified = local_user_view.local_user.email_verified;
let form = LocalUserUpdateForm {
// necessary in case this is a new signup
@ -27,18 +32,16 @@ pub async fn verify_email(
email: Some(Some(verification.email)),
..Default::default()
};
let local_user_id = verification.local_user_id;
LocalUser::update(&mut context.pool(), local_user_id, &form).await?;
EmailVerification::delete_old_tokens_for_local_user(&mut context.pool(), local_user_id).await?;
// send out notification about registration application to admins if enabled
if site_view.local_site.application_email_admins {
let local_user = LocalUserView::read(&mut context.pool(), local_user_id).await?;
// Send out notification about registration application to admins if enabled, and the user hasn't
// already been verified.
if site_view.local_site.application_email_admins && !email_already_verified {
send_new_applicant_email_to_admins(
&local_user.person.name,
&local_user_view.person.name,
&mut context.pool(),
context.settings(),
)

View file

@ -4,6 +4,7 @@ use lemmy_db_schema::{
source::{login_token::LoginToken, site::Site},
CommentSortType,
ListingType,
PersonContentType,
PostListingMode,
PostSortType,
};
@ -249,6 +250,8 @@ pub struct GetPersonDetailsResponse {
///
/// Either person_id, or username are required.
pub struct ListPersonContent {
#[cfg_attr(feature = "full", ts(optional))]
pub type_: Option<PersonContentType>,
#[cfg_attr(feature = "full", ts(optional))]
pub person_id: Option<PersonId>,
/// Example: dessalines , or dessalines@xyz.tld
@ -275,6 +278,8 @@ pub struct ListPersonContentResponse {
#[cfg_attr(feature = "full", ts(export))]
/// Gets your saved posts and comments
pub struct ListPersonSaved {
#[cfg_attr(feature = "full", ts(optional))]
pub type_: Option<PersonContentType>,
#[cfg_attr(feature = "full", ts(optional))]
pub page_cursor: Option<PersonSavedCombinedPaginationCursor>,
#[cfg_attr(feature = "full", ts(optional))]

View file

@ -97,6 +97,10 @@ pub struct GetPosts {
#[cfg_attr(feature = "full", ts(optional))]
pub community_name: Option<String>,
#[cfg_attr(feature = "full", ts(optional))]
pub saved_only: Option<bool>,
#[cfg_attr(feature = "full", ts(optional))]
pub read_only: Option<bool>,
#[cfg_attr(feature = "full", ts(optional))]
pub liked_only: Option<bool>,
#[cfg_attr(feature = "full", ts(optional))]
pub disliked_only: Option<bool>,

View file

@ -32,7 +32,7 @@ use reqwest::{
};
use reqwest_middleware::ClientWithMiddleware;
use serde::{Deserialize, Serialize};
use tracing::info;
use tracing::{info, warn};
use url::Url;
use urlencoding::encode;
use webpage::HTML;
@ -173,15 +173,23 @@ pub async fn generate_post_link_metadata(
metadata.opengraph_data.image.clone()
};
// Attempt to generate a thumbnail depending on the instance settings. Either by proxying,
// storing image persistently in pict-rs or returning the remote url directly as thumbnail.
let thumbnail_url = if let (false, Some(url)) = (is_image_post, custom_thumbnail) {
proxy_image_link(url, &context).await.ok()
} else if let (true, Some(url)) = (allow_generate_thumbnail, image_url) {
proxy_image_link(url.clone(), &context)
.await
.map_err(|e| warn!("Failed to proxy thumbnail: {e}"))
.ok()
.or(Some(url.into()))
} else if let (true, Some(url)) = (allow_generate_thumbnail, image_url.clone()) {
generate_pictrs_thumbnail(&url, &context)
.await
.map_err(|e| warn!("Failed to generate thumbnail: {e}"))
.ok()
.map(Into::into)
.or(image_url)
} else {
metadata.opengraph_data.image.clone()
image_url.clone()
};
let form = PostUpdateForm {

View file

@ -9,7 +9,7 @@ use actix_web::web::{Json, Query};
use lemmy_api_common::{
comment::{GetComments, GetCommentsResponse},
context::LemmyContext,
utils::check_private_instance,
utils::{check_conflicting_like_filters, check_private_instance},
};
use lemmy_db_schema::{
source::{comment::Comment, community::Community},
@ -19,7 +19,7 @@ use lemmy_db_views::{
comment_view::CommentQuery,
structs::{LocalUserView, SiteView},
};
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType, LemmyResult};
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
#[tracing::instrument(skip(context))]
pub async fn list_comments(
@ -49,9 +49,7 @@ pub async fn list_comments(
let liked_only = data.liked_only;
let disliked_only = data.disliked_only;
if liked_only.unwrap_or_default() && disliked_only.unwrap_or_default() {
return Err(LemmyError::from(LemmyErrorType::ContradictingFilters));
}
check_conflicting_like_filters(liked_only, disliked_only)?;
let page = data.page;
let limit = data.limit;

View file

@ -37,9 +37,11 @@ pub async fn list_person_content(
None
};
let page_back = data.page_back;
let type_ = data.type_;
let content = PersonContentCombinedQuery {
creator_id: person_details_id,
type_,
page_after,
page_back,
}

View file

@ -41,6 +41,7 @@ pub async fn list_posts(
} else {
data.community_id
};
let read_only = data.read_only;
let show_hidden = data.show_hidden;
let show_read = data.show_read;
let show_nsfw = data.show_nsfw;
@ -76,6 +77,7 @@ pub async fn list_posts(
listing_type,
sort,
community_id,
read_only,
liked_only,
disliked_only,
page,

View file

@ -219,6 +219,16 @@ pub enum ModlogActionType {
AdminAllowInstance,
}
#[derive(EnumString, Display, Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
/// A list of possible types for the various modlog actions.
pub enum PersonContentType {
All,
Comments,
Posts,
}
#[derive(
EnumString, Display, Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash,
)]

View file

@ -45,6 +45,7 @@ use lemmy_db_schema::{
community::CommunityFollower,
},
utils::{actions, actions_alias, functions::coalesce, get_conn, DbPool},
PersonContentType,
};
use lemmy_utils::error::LemmyResult;
@ -84,6 +85,8 @@ pub struct PaginationCursorData(PersonContentCombined);
pub struct PersonContentCombinedQuery {
pub creator_id: PersonId,
#[new(default)]
pub type_: Option<PersonContentType>,
#[new(default)]
pub page_after: Option<PaginationCursorData>,
#[new(default)]
pub page_back: Option<bool>,
@ -210,6 +213,16 @@ impl PersonContentCombinedQuery {
let mut query = PaginatedQueryBuilder::new(query);
if let Some(type_) = self.type_ {
query = match type_ {
PersonContentType::All => query,
PersonContentType::Comments => {
query.filter(person_content_combined::comment_id.is_not_null())
}
PersonContentType::Posts => query.filter(person_content_combined::post_id.is_not_null()),
}
}
let page_after = self.page_after.map(|c| c.0);
if self.page_back.unwrap_or_default() {

View file

@ -42,6 +42,7 @@ use lemmy_db_schema::{
community::CommunityFollower,
},
utils::{actions, actions_alias, functions::coalesce, get_conn, DbPool},
PersonContentType,
};
use lemmy_utils::error::LemmyResult;
@ -79,6 +80,7 @@ pub struct PaginationCursorData(PersonSavedCombined);
#[derive(Default)]
pub struct PersonSavedCombinedQuery {
pub type_: Option<PersonContentType>,
pub page_after: Option<PaginationCursorData>,
pub page_back: Option<bool>,
}
@ -212,6 +214,16 @@ impl PersonSavedCombinedQuery {
let mut query = PaginatedQueryBuilder::new(query);
if let Some(type_) = self.type_ {
query = match type_ {
PersonContentType::All => query,
PersonContentType::Comments => {
query.filter(person_saved_combined::comment_id.is_not_null())
}
PersonContentType::Posts => query.filter(person_saved_combined::post_id.is_not_null()),
}
}
let page_after = self.page_after.map(|c| c.0);
if self.page_back.unwrap_or_default() {

View file

@ -306,6 +306,12 @@ fn queries<'a>() -> Queries<
query = query.filter(post_aggregates::comments.eq(0));
};
if o.read_only.unwrap_or_default() {
query = query
.filter(post_actions::read.is_not_null())
.then_order_by(post_actions::read.desc())
}
if !o.show_read.unwrap_or(o.local_user.show_read_posts()) {
// Do not hide read posts when it is a user profile view
// Or, only hide read posts on non-profile views
@ -496,6 +502,7 @@ pub struct PostQuery<'a> {
pub local_user: Option<&'a LocalUser>,
pub search_term: Option<String>,
pub url_only: Option<bool>,
pub read_only: Option<bool>,
pub liked_only: Option<bool>,
pub disliked_only: Option<bool>,
pub title_only: Option<bool>,
@ -1192,6 +1199,34 @@ mod tests {
Ok(())
}
#[test_context(Data)]
#[tokio::test]
#[serial]
async fn post_listing_read_only(data: &mut Data) -> LemmyResult<()> {
let pool = &data.pool();
let pool = &mut pool.into();
// Only mark the bot post as read
// The read_only should only show the bot post
let post_read_form =
PostReadForm::new(data.inserted_bot_post.id, data.local_user_view.person.id);
PostRead::mark_as_read(pool, &post_read_form).await?;
// Only read the post marked as read
let read_read_post_listing = PostQuery {
community_id: Some(data.inserted_community.id),
read_only: Some(true),
..data.default_post_query()
}
.list(&data.site, pool)
.await?;
// This should only include the bot post, not the one you created
assert_eq!(vec![POST_BY_BOT], names(&read_read_post_listing));
Ok(())
}
#[test_context(Data)]
#[tokio::test]
#[serial]

View file

@ -0,0 +1,2 @@
DROP INDEX idx_post_actions_on_read_read_not_null;

View file

@ -0,0 +1,4 @@
CREATE INDEX idx_post_actions_on_read_read_not_null ON post_actions (person_id, read, post_id)
WHERE
read IS NOT NULL;