Adding views and replaceable schema.

This commit is contained in:
Dessalines 2024-12-06 09:49:11 -05:00
parent 724856d684
commit 1053df1a4b
7 changed files with 155 additions and 162 deletions

View file

@ -7,7 +7,11 @@ use lemmy_db_schema::{
PostListingMode, PostListingMode,
PostSortType, PostSortType,
}; };
use lemmy_db_views::structs::{CommentView, LocalImageView, PostView}; use lemmy_db_views::structs::{
LocalImageView,
ProfileCombinedPaginationCursor,
ProfileCombinedView,
};
use lemmy_db_views_actor::structs::{ use lemmy_db_views_actor::structs::{
CommentReplyView, CommentReplyView,
CommunityModeratorView, CommunityModeratorView,
@ -223,15 +227,13 @@ pub struct GetPersonDetails {
#[cfg_attr(feature = "full", ts(optional))] #[cfg_attr(feature = "full", ts(optional))]
pub username: Option<String>, pub username: Option<String>,
#[cfg_attr(feature = "full", ts(optional))] #[cfg_attr(feature = "full", ts(optional))]
pub sort: Option<PostSortType>,
#[cfg_attr(feature = "full", ts(optional))]
pub page: Option<i64>,
#[cfg_attr(feature = "full", ts(optional))]
pub limit: Option<i64>,
#[cfg_attr(feature = "full", ts(optional))]
pub community_id: Option<CommunityId>, pub community_id: Option<CommunityId>,
#[cfg_attr(feature = "full", ts(optional))] #[cfg_attr(feature = "full", ts(optional))]
pub saved_only: Option<bool>, pub saved_only: Option<bool>,
#[cfg_attr(feature = "full", ts(optional))]
pub page_cursor: Option<ProfileCombinedPaginationCursor>,
#[cfg_attr(feature = "full", ts(optional))]
pub page_back: Option<bool>,
} }
#[skip_serializing_none] #[skip_serializing_none]
@ -243,8 +245,7 @@ pub struct GetPersonDetailsResponse {
pub person_view: PersonView, pub person_view: PersonView,
#[cfg_attr(feature = "full", ts(optional))] #[cfg_attr(feature = "full", ts(optional))]
pub site: Option<Site>, pub site: Option<Site>,
pub comments: Vec<CommentView>, pub content: Vec<ProfileCombinedView>,
pub posts: Vec<PostView>,
pub moderates: Vec<CommunityModeratorView>, pub moderates: Vec<CommunityModeratorView>,
} }

View file

@ -6,10 +6,9 @@ use lemmy_api_common::{
person::{GetPersonDetails, GetPersonDetailsResponse}, person::{GetPersonDetails, GetPersonDetailsResponse},
utils::{check_private_instance, read_site_for_actor}, utils::{check_private_instance, read_site_for_actor},
}; };
use lemmy_db_schema::{source::person::Person, utils::post_to_comment_sort_type}; use lemmy_db_schema::source::person::Person;
use lemmy_db_views::{ use lemmy_db_views::{
comment_view::CommentQuery, profile_combined_view::ProfileCombinedQuery,
post_view::PostQuery,
structs::{LocalUserView, SiteView}, structs::{LocalUserView, SiteView},
}; };
use lemmy_db_views_actor::structs::{CommunityModeratorView, PersonView}; use lemmy_db_views_actor::structs::{CommunityModeratorView, PersonView};
@ -47,46 +46,38 @@ pub async fn read_person(
// `my_user` // `my_user`
let person_view = PersonView::read(&mut context.pool(), person_details_id).await?; let person_view = PersonView::read(&mut context.pool(), person_details_id).await?;
let sort = data.sort; // parse pagination token
let page = data.page; let page_after = if let Some(pa) = &data.page_cursor {
let limit = data.limit; Some(pa.read(&mut context.pool()).await?)
let saved_only = data.saved_only;
let community_id = data.community_id;
// If its saved only, you don't care what creator it was
// Or, if its not saved, then you only want it for that specific creator
let creator_id = if !saved_only.unwrap_or_default() {
Some(person_details_id)
} else { } else {
None None
}; };
let page_back = data.page_back;
let saved_only = data.saved_only;
let community_id = data.community_id;
let local_user = local_user_view.as_ref().map(|l| &l.local_user); // If its saved only, then ignore the person details id,
// and use your local user's id
let creator_id = if !saved_only.unwrap_or_default() {
Some(person_details_id)
} else {
local_user_view.as_ref().map(|u| u.local_user.person_id)
};
let posts = PostQuery { let content = if let Some(creator_id) = creator_id {
sort, ProfileCombinedQuery {
saved_only,
local_user,
community_id,
page,
limit,
creator_id, creator_id,
..Default::default()
}
.list(&local_site.site, &mut context.pool())
.await?;
let comments = CommentQuery {
local_user,
sort: sort.map(post_to_comment_sort_type),
saved_only,
community_id, community_id,
page, saved_only,
limit, page_after,
creator_id, page_back,
..Default::default()
} }
.list(&local_site.site, &mut context.pool()) .list(&mut context.pool(), &local_user_view)
.await?; .await?
} else {
// if the creator is missing (saved_only, and no local_user), then return empty content
Vec::new()
};
let moderates = CommunityModeratorView::for_person( let moderates = CommunityModeratorView::for_person(
&mut context.pool(), &mut context.pool(),
@ -97,12 +88,10 @@ pub async fn read_person(
let site = read_site_for_actor(person_view.person.actor_id.clone(), &context).await?; let site = read_site_for_actor(person_view.person.actor_id.clone(), &context).await?;
// Return the jwt
Ok(Json(GetPersonDetailsResponse { Ok(Json(GetPersonDetailsResponse {
person_view, person_view,
site, site,
moderates, moderates,
comments, content,
posts,
})) }))
} }

View file

@ -685,3 +685,31 @@ CALL r.create_report_combined_trigger ('comment_report');
CALL r.create_report_combined_trigger ('private_message_report'); CALL r.create_report_combined_trigger ('private_message_report');
-- Profile (comment, post)
CREATE PROCEDURE r.create_profile_combined_trigger (table_name text)
LANGUAGE plpgsql
AS $a$
BEGIN
EXECUTE replace($b$ CREATE FUNCTION r.profile_combined_thing_insert ( )
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
BEGIN
INSERT INTO profile_combined (published, thing_id)
VALUES (NEW.published, NEW.id);
RETURN NEW;
END $$;
CREATE TRIGGER profile_combined
AFTER INSERT ON thing
FOR EACH ROW
EXECUTE FUNCTION r.profile_combined_thing_insert ( );
$b$,
'thing',
table_name);
END;
$a$;
CALL r.create_profile_combined_trigger ('post');
CALL r.create_profile_combined_trigger ('comment');

View file

@ -18,14 +18,15 @@ use diesel::{
use diesel_async::RunQueryDsl; use diesel_async::RunQueryDsl;
use i_love_jesus::PaginatedQueryBuilder; use i_love_jesus::PaginatedQueryBuilder;
use lemmy_db_schema::{ use lemmy_db_schema::{
aliases::{self, creator_community_actions}, aliases::creator_community_actions,
newtypes::CommunityId, newtypes::{CommunityId, PersonId},
schema::{ schema::{
comment, comment,
comment_actions, comment_actions,
comment_aggregates, comment_aggregates,
community, community,
community_actions, community_actions,
image_details,
local_user, local_user,
person, person,
person_actions, person_actions,
@ -74,11 +75,11 @@ impl ProfileCombinedPaginationCursor {
#[derive(Clone)] #[derive(Clone)]
pub struct PaginationCursorData(ProfileCombined); pub struct PaginationCursorData(ProfileCombined);
// TODO check these
#[derive(Default)] #[derive(Default)]
pub struct ProfileCombinedQuery { pub struct ProfileCombinedQuery {
pub creator_id: PersonId,
pub community_id: Option<CommunityId>, pub community_id: Option<CommunityId>,
pub unresolved_only: Option<bool>, pub saved_only: Option<bool>,
pub page_after: Option<PaginationCursorData>, pub page_after: Option<PaginationCursorData>,
pub page_back: Option<bool>, pub page_back: Option<bool>,
} }
@ -87,10 +88,12 @@ impl ProfileCombinedQuery {
pub async fn list( pub async fn list(
self, self,
pool: &mut DbPool<'_>, pool: &mut DbPool<'_>,
user: &LocalUserView, user: &Option<LocalUserView>,
) -> LemmyResult<Vec<ProfileCombinedView>> { ) -> LemmyResult<Vec<ProfileCombinedView>> {
let my_person_id = user.local_user.person_id; let my_person_id = user
// let item_creator = aliases::person1.field(person::id); .as_ref()
.map(|u| u.local_user.person_id)
.unwrap_or(PersonId(-1));
let item_creator = person::id; let item_creator = person::id;
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
@ -114,20 +117,11 @@ impl ProfileCombinedQuery {
// The item creator // The item creator
.inner_join( .inner_join(
person::table.on( person::table.on(
post::creator_id comment::creator_id
.eq(person::id) .eq(person::id)
.or(comment::creator_id.eq(person::id)), .or(post::creator_id.eq(person::id)),
), ),
) )
// The item creator
// You can now use aliases::person1.field(person::id) / item_creator for all the item actions
// .inner_join(
// aliases::person1.on(
// post::creator_id
// .eq(item_creator)
// .or(comment::creator_id.eq(item_creator)),
// ),
// )
// The community // The community
.inner_join(community::table.on(post::community_id.eq(community::id))) .inner_join(community::table.on(post::community_id.eq(community::id)))
.left_join(actions_alias( .left_join(actions_alias(
@ -153,10 +147,7 @@ impl ProfileCombinedQuery {
Some(my_person_id), Some(my_person_id),
item_creator, item_creator,
)) ))
.left_join( .inner_join(post_aggregates::table.on(post::id.eq(post_aggregates::post_id)))
post_aggregates::table
.on(profile_combined::post_id.eq(post_aggregates::post_id.nullable())),
)
.left_join( .left_join(
comment_aggregates::table comment_aggregates::table
.on(profile_combined::comment_id.eq(comment_aggregates::comment_id.nullable())), .on(profile_combined::comment_id.eq(comment_aggregates::comment_id.nullable())),
@ -166,43 +157,42 @@ impl ProfileCombinedQuery {
Some(my_person_id), Some(my_person_id),
comment::id, comment::id,
)) ))
.left_join(image_details::table.on(post::thumbnail_url.eq(image_details::link.nullable())))
// The creator id filter
.filter(item_creator.eq(self.creator_id))
.select(( .select((
// Post-specific // Post-specific
post::all_columns.nullable(), post_aggregates::all_columns,
// post_aggregates::all_columns.nullable(), coalesce(
// coalesce( post_aggregates::comments.nullable() - post_actions::read_comments_amount.nullable(),
// post_aggregates::comments.nullable() - post_actions::read_comments_amount.nullable(), post_aggregates::comments,
// post_aggregates::comments, ),
// ) post_actions::saved.nullable().is_not_null(),
// .nullable(), post_actions::read.nullable().is_not_null(),
// post_actions::saved.nullable().is_not_null(), post_actions::hidden.nullable().is_not_null(),
// post_actions::read.nullable().is_not_null(), post_actions::like_score.nullable(),
// post_actions::hidden.nullable().is_not_null(), image_details::all_columns.nullable(),
// post_actions::like_score.nullable(), // Comment-specific
// // Comment-specific comment::all_columns.nullable(),
// comment::all_columns.nullable(), comment_aggregates::all_columns.nullable(),
// comment_aggregates::all_columns.nullable(), comment_actions::saved.nullable().is_not_null(),
// comment_actions::saved.nullable().is_not_null(), comment_actions::like_score.nullable(),
// comment_actions::like_score.nullable(), // Shared
// // Private-message-specific post::all_columns,
// private_message_profile::all_columns.nullable(), community::all_columns,
// private_message::all_columns.nullable(), person::all_columns,
// // Shared CommunityFollower::select_subscribed_type(),
// person::all_columns, local_user::admin.nullable().is_not_null(),
// aliases::person1.fields(person::all_columns), creator_community_actions
// community::all_columns.nullable(), .field(community_actions::became_moderator)
// CommunityFollower::select_subscribed_type(), .nullable()
// aliases::person2.fields(person::all_columns.nullable()), .is_not_null(),
// local_user::admin.nullable().is_not_null(), creator_community_actions
// creator_community_actions .field(community_actions::received_ban)
// .field(community_actions::received_ban) .nullable()
// .nullable() .is_not_null(),
// .is_not_null(), person_actions::blocked.nullable().is_not_null(),
// creator_community_actions community_actions::received_ban.nullable().is_not_null(),
// .field(community_actions::became_moderator)
// .nullable()
// .is_not_null(),
// person_actions::blocked.nullable().is_not_null(),
)) ))
.into_boxed(); .into_boxed();
@ -210,9 +200,13 @@ impl ProfileCombinedQuery {
query = query.filter(community::id.eq(community_id)); query = query.filter(community::id.eq(community_id));
} }
// If its not an admin, get only the ones you mod // If its saved only, then filter
if !user.local_user.admin { if self.saved_only.unwrap_or_default() {
query = query.filter(community_actions::became_moderator.is_not_null()); query = query.filter(
comment_actions::saved
.is_not_null()
.or(post_actions::saved.is_not_null()),
)
} }
let mut query = PaginatedQueryBuilder::new(query); let mut query = PaginatedQueryBuilder::new(query);
@ -225,25 +219,11 @@ impl ProfileCombinedQuery {
query = query.after(page_after); query = query.after(page_after);
} }
// If viewing all profiles, order by newest, but if viewing unresolved only, show the oldest // Sorting by published
// first (FIFO)
if self.unresolved_only.unwrap_or_default() {
query = query query = query
.filter( .then_desc(ReverseTimestampKey(key::published))
post_profile::resolved
.eq(false)
.or(comment_profile::resolved.eq(false))
.or(private_message_profile::resolved.eq(false)),
)
// TODO: when a `then_asc` method is added, use it here, make the id sort direction match,
// and remove the separate index; unless additional columns are added to this sort
.then_desc(ReverseTimestampKey(key::published));
} else {
query = query.then_desc(key::published);
}
// Tie breaker // Tie breaker
query = query.then_desc(key::id); .then_desc(key::id);
let res = query.load::<ProfileCombinedViewInternal>(conn).await?; let res = query.load::<ProfileCombinedViewInternal>(conn).await?;
@ -259,14 +239,28 @@ fn map_to_enum(view: ProfileCombinedViewInternal) -> Option<ProfileCombinedView>
// Use for a short alias // Use for a short alias
let v = view; let v = view;
if let (Some(post), Some(community), Some(unread_comments), Some(counts)) = if let (Some(comment), Some(counts)) = (v.comment, v.comment_counts) {
(v.post, v.community, v.post_unread_comments, v.post_counts) Some(ProfileCombinedView::Comment(CommentView {
{ comment,
Some(ProfileCombinedView::Post(PostView {
post,
community,
unread_comments,
counts, counts,
post: v.post,
community: v.community,
creator: v.item_creator,
creator_banned_from_community: v.item_creator_banned_from_community,
creator_is_moderator: v.item_creator_is_moderator,
creator_is_admin: v.item_creator_is_admin,
creator_blocked: v.item_creator_blocked,
subscribed: v.subscribed,
saved: v.comment_saved,
my_vote: v.my_comment_vote,
banned_from_community: v.banned_from_community,
}))
} else {
Some(ProfileCombinedView::Post(PostView {
post: v.post,
community: v.community,
unread_comments: v.post_unread_comments,
counts: v.post_counts,
creator: v.item_creator, creator: v.item_creator,
creator_banned_from_community: v.item_creator_banned_from_community, creator_banned_from_community: v.item_creator_banned_from_community,
creator_is_moderator: v.item_creator_is_moderator, creator_is_moderator: v.item_creator_is_moderator,
@ -277,28 +271,8 @@ fn map_to_enum(view: ProfileCombinedViewInternal) -> Option<ProfileCombinedView>
read: v.post_read, read: v.post_read,
hidden: v.post_hidden, hidden: v.post_hidden,
my_vote: v.my_post_vote, my_vote: v.my_post_vote,
image_details: v.image_details,
banned_from_community: v.banned_from_community,
})) }))
} else if let (Some(comment), Some(counts), Some(post), Some(community)) = (
v.comment,
v.comment_counts,
v.post.clone(),
v.community.clone(),
) {
Some(ProfileCombinedView::Comment(CommentView {
comment,
counts,
post,
community,
creator: v.item_creator,
creator_banned_from_community: v.item_creator_banned_from_community,
creator_is_moderator: v.item_creator_is_moderator,
creator_is_admin: v.item_creator_is_admin,
creator_blocked: v.item_creator_blocked,
subscribed: v.subscribed,
saved: v.comment_saved,
my_vote: v.my_comment_vote,
}))
} else {
None
} }
} }

View file

@ -310,8 +310,8 @@ pub struct ProfileCombinedViewInternal {
pub my_post_vote: Option<i16>, pub my_post_vote: Option<i16>,
pub image_details: Option<ImageDetails>, pub image_details: Option<ImageDetails>,
// Comment-specific // Comment-specific
pub comment: Comment, pub comment: Option<Comment>,
pub comment_counts: CommentAggregates, pub comment_counts: Option<CommentAggregates>,
pub comment_saved: bool, pub comment_saved: bool,
pub my_comment_vote: Option<i16>, pub my_comment_vote: Option<i16>,
// Shared // Shared
@ -323,7 +323,6 @@ pub struct ProfileCombinedViewInternal {
pub item_creator_is_moderator: bool, pub item_creator_is_moderator: bool,
pub item_creator_banned_from_community: bool, pub item_creator_banned_from_community: bool,
pub item_creator_blocked: bool, pub item_creator_blocked: bool,
pub item_saved: bool,
pub banned_from_community: bool, pub banned_from_community: bool,
} }

View file

@ -1 +1,2 @@
DROP TABLE profile_combined; DROP TABLE profile_combined;

View file

@ -4,7 +4,7 @@ CREATE TABLE profile_combined (
id serial PRIMARY KEY, id serial PRIMARY KEY,
published timestamptz NOT NULL, published timestamptz NOT NULL,
post_id int UNIQUE REFERENCES post ON UPDATE CASCADE ON DELETE CASCADE, post_id int UNIQUE REFERENCES post ON UPDATE CASCADE ON DELETE CASCADE,
comment_id int UNIQUE REFERENCES comment ON UPDATE CASCADE ON DELETE CASCADE, comment_id int UNIQUE REFERENCES COMMENT ON UPDATE CASCADE ON DELETE CASCADE,
-- Make sure only one of the columns is not null -- Make sure only one of the columns is not null
CHECK ((post_id IS NOT NULL)::integer + (comment_id IS NOT NULL)::integer = 1) CHECK ((post_id IS NOT NULL)::integer + (comment_id IS NOT NULL)::integer = 1)
); );
@ -27,3 +27,4 @@ SELECT
id id
FROM FROM
comment; comment;