mirror of
https://github.com/LemmyNet/lemmy.git
synced 2024-12-12 05:54:51 +00:00
A few changes to profile view.
- Separating the profile fetch from its combined content fetch. - Starting to separate saved_only into its own combined view.
This commit is contained in:
parent
1053df1a4b
commit
32b5411abd
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -2677,6 +2677,7 @@ version = "0.19.6-beta.7"
|
|||
dependencies = [
|
||||
"actix-web",
|
||||
"chrono",
|
||||
"derive-new",
|
||||
"diesel",
|
||||
"diesel-async",
|
||||
"diesel_ltree",
|
||||
|
|
0
crates/api/src/local_user/list_saved.rs
Normal file
0
crates/api/src/local_user/list_saved.rs
Normal file
|
@ -8,6 +8,7 @@ pub mod get_captcha;
|
|||
pub mod list_banned;
|
||||
pub mod list_logins;
|
||||
pub mod list_media;
|
||||
pub mod list_saved;
|
||||
pub mod login;
|
||||
pub mod logout;
|
||||
pub mod notifications;
|
||||
|
|
|
@ -131,8 +131,6 @@ pub struct GetComments {
|
|||
#[cfg_attr(feature = "full", ts(optional))]
|
||||
pub parent_id: Option<CommentId>,
|
||||
#[cfg_attr(feature = "full", ts(optional))]
|
||||
pub saved_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>,
|
||||
|
|
|
@ -9,8 +9,8 @@ use lemmy_db_schema::{
|
|||
};
|
||||
use lemmy_db_views::structs::{
|
||||
LocalImageView,
|
||||
ProfileCombinedPaginationCursor,
|
||||
ProfileCombinedView,
|
||||
PersonContentCombinedPaginationCursor,
|
||||
PersonContentCombinedView,
|
||||
};
|
||||
use lemmy_db_views_actor::structs::{
|
||||
CommentReplyView,
|
||||
|
@ -226,14 +226,6 @@ pub struct GetPersonDetails {
|
|||
/// Example: dessalines , or dessalines@xyz.tld
|
||||
#[cfg_attr(feature = "full", ts(optional))]
|
||||
pub username: Option<String>,
|
||||
#[cfg_attr(feature = "full", ts(optional))]
|
||||
pub community_id: Option<CommunityId>,
|
||||
#[cfg_attr(feature = "full", ts(optional))]
|
||||
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]
|
||||
|
@ -245,10 +237,58 @@ pub struct GetPersonDetailsResponse {
|
|||
pub person_view: PersonView,
|
||||
#[cfg_attr(feature = "full", ts(optional))]
|
||||
pub site: Option<Site>,
|
||||
pub content: Vec<ProfileCombinedView>,
|
||||
pub moderates: Vec<CommunityModeratorView>,
|
||||
}
|
||||
|
||||
#[skip_serializing_none]
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "full", derive(TS))]
|
||||
#[cfg_attr(feature = "full", ts(export))]
|
||||
/// Gets a person's content (posts and comments)
|
||||
///
|
||||
/// Either person_id, or username are required.
|
||||
pub struct ListPersonContent {
|
||||
#[cfg_attr(feature = "full", ts(optional))]
|
||||
pub person_id: Option<PersonId>,
|
||||
/// Example: dessalines , or dessalines@xyz.tld
|
||||
#[cfg_attr(feature = "full", ts(optional))]
|
||||
pub username: Option<String>,
|
||||
#[cfg_attr(feature = "full", ts(optional))]
|
||||
pub page_cursor: Option<PersonContentCombinedPaginationCursor>,
|
||||
#[cfg_attr(feature = "full", ts(optional))]
|
||||
pub page_back: Option<bool>,
|
||||
}
|
||||
|
||||
#[skip_serializing_none]
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[cfg_attr(feature = "full", derive(TS))]
|
||||
#[cfg_attr(feature = "full", ts(export))]
|
||||
/// A person's content response.
|
||||
pub struct ListPersonContentResponse {
|
||||
pub content: Vec<PersonContentCombinedView>,
|
||||
}
|
||||
|
||||
#[skip_serializing_none]
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "full", derive(TS))]
|
||||
#[cfg_attr(feature = "full", ts(export))]
|
||||
/// Gets your saved posts and comments
|
||||
pub struct ListSaved {
|
||||
#[cfg_attr(feature = "full", ts(optional))]
|
||||
pub page_cursor: Option<PersonContentCombinedPaginationCursor>,
|
||||
#[cfg_attr(feature = "full", ts(optional))]
|
||||
pub page_back: Option<bool>,
|
||||
}
|
||||
|
||||
#[skip_serializing_none]
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[cfg_attr(feature = "full", derive(TS))]
|
||||
#[cfg_attr(feature = "full", ts(export))]
|
||||
/// A person's saved content response.
|
||||
pub struct ListSavedResponse {
|
||||
pub saved: Vec<PersonContentCombinedView>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "full", derive(TS))]
|
||||
#[cfg_attr(feature = "full", ts(export))]
|
||||
|
|
|
@ -95,8 +95,6 @@ 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 liked_only: Option<bool>,
|
||||
#[cfg_attr(feature = "full", ts(optional))]
|
||||
pub disliked_only: Option<bool>,
|
||||
|
|
|
@ -94,8 +94,6 @@ pub struct Search {
|
|||
#[cfg_attr(feature = "full", ts(optional))]
|
||||
pub post_url_only: Option<bool>,
|
||||
#[cfg_attr(feature = "full", ts(optional))]
|
||||
pub saved_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>,
|
||||
|
|
|
@ -46,7 +46,6 @@ pub async fn list_comments(
|
|||
&site_view.local_site,
|
||||
));
|
||||
let max_depth = data.max_depth;
|
||||
let saved_only = data.saved_only;
|
||||
|
||||
let liked_only = data.liked_only;
|
||||
let disliked_only = data.disliked_only;
|
||||
|
@ -80,7 +79,6 @@ pub async fn list_comments(
|
|||
listing_type,
|
||||
sort,
|
||||
max_depth,
|
||||
saved_only,
|
||||
liked_only,
|
||||
disliked_only,
|
||||
community_id,
|
||||
|
|
50
crates/apub/src/api/list_person_content.rs
Normal file
50
crates/apub/src/api/list_person_content.rs
Normal file
|
@ -0,0 +1,50 @@
|
|||
use super::resolve_person_id_from_id_or_username;
|
||||
use activitypub_federation::config::Data;
|
||||
use actix_web::web::{Json, Query};
|
||||
use lemmy_api_common::{
|
||||
context::LemmyContext,
|
||||
person::{ListPersonContent, ListPersonContentResponse},
|
||||
utils::check_private_instance,
|
||||
};
|
||||
use lemmy_db_views::{
|
||||
person_content_combined_view::PersonContentCombinedQuery,
|
||||
structs::{LocalUserView, SiteView},
|
||||
};
|
||||
use lemmy_utils::error::LemmyResult;
|
||||
|
||||
#[tracing::instrument(skip(context))]
|
||||
pub async fn list_person_content(
|
||||
data: Query<ListPersonContent>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: Option<LocalUserView>,
|
||||
) -> LemmyResult<Json<ListPersonContentResponse>> {
|
||||
let local_site = SiteView::read_local(&mut context.pool()).await?;
|
||||
|
||||
check_private_instance(&local_user_view, &local_site.local_site)?;
|
||||
|
||||
let person_details_id = resolve_person_id_from_id_or_username(
|
||||
&data.person_id,
|
||||
&data.username,
|
||||
&context,
|
||||
&local_user_view,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// parse pagination token
|
||||
let page_after = if let Some(pa) = &data.page_cursor {
|
||||
Some(pa.read(&mut context.pool()).await?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let page_back = data.page_back;
|
||||
|
||||
let content = PersonContentCombinedQuery {
|
||||
creator_id: person_details_id,
|
||||
page_after,
|
||||
page_back,
|
||||
}
|
||||
.list(&mut context.pool(), &local_user_view)
|
||||
.await?;
|
||||
|
||||
Ok(Json(ListPersonContentResponse { content }))
|
||||
}
|
|
@ -41,7 +41,6 @@ pub async fn list_posts(
|
|||
} else {
|
||||
data.community_id
|
||||
};
|
||||
let saved_only = data.saved_only;
|
||||
let show_hidden = data.show_hidden;
|
||||
let show_read = data.show_read;
|
||||
let show_nsfw = data.show_nsfw;
|
||||
|
@ -77,7 +76,6 @@ pub async fn list_posts(
|
|||
listing_type,
|
||||
sort,
|
||||
community_id,
|
||||
saved_only,
|
||||
liked_only,
|
||||
disliked_only,
|
||||
page,
|
||||
|
|
|
@ -1,12 +1,18 @@
|
|||
use crate::{fetcher::resolve_actor_identifier, objects::person::ApubPerson};
|
||||
use activitypub_federation::config::Data;
|
||||
use lemmy_api_common::{context::LemmyContext, LemmyErrorType};
|
||||
use lemmy_db_schema::{
|
||||
newtypes::CommunityId,
|
||||
source::{local_site::LocalSite, local_user::LocalUser},
|
||||
newtypes::{CommunityId, PersonId},
|
||||
source::{local_site::LocalSite, local_user::LocalUser, person::Person},
|
||||
CommentSortType,
|
||||
ListingType,
|
||||
PostSortType,
|
||||
};
|
||||
use lemmy_db_views::structs::LocalUserView;
|
||||
use lemmy_utils::error::LemmyResult;
|
||||
|
||||
pub mod list_comments;
|
||||
pub mod list_person_content;
|
||||
pub mod list_posts;
|
||||
pub mod read_community;
|
||||
pub mod read_person;
|
||||
|
@ -61,3 +67,28 @@ fn comment_sort_type_with_default(
|
|||
.unwrap_or(local_site.default_comment_sort_type),
|
||||
)
|
||||
}
|
||||
|
||||
async fn resolve_person_id_from_id_or_username(
|
||||
person_id: &Option<PersonId>,
|
||||
username: &Option<String>,
|
||||
context: &Data<LemmyContext>,
|
||||
local_user_view: &Option<LocalUserView>,
|
||||
) -> LemmyResult<PersonId> {
|
||||
// Check to make sure a person name or an id is given
|
||||
if username.is_none() && person_id.is_none() {
|
||||
Err(LemmyErrorType::NoIdGiven)?
|
||||
}
|
||||
|
||||
Ok(match person_id {
|
||||
Some(id) => *id,
|
||||
None => {
|
||||
if let Some(username) = username {
|
||||
resolve_actor_identifier::<ApubPerson, Person>(username, context, local_user_view, true)
|
||||
.await?
|
||||
.id
|
||||
} else {
|
||||
Err(LemmyErrorType::NotFound)?
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::{fetcher::resolve_actor_identifier, objects::person::ApubPerson};
|
||||
use super::resolve_person_id_from_id_or_username;
|
||||
use activitypub_federation::config::Data;
|
||||
use actix_web::web::{Json, Query};
|
||||
use lemmy_api_common::{
|
||||
|
@ -6,13 +6,9 @@ use lemmy_api_common::{
|
|||
person::{GetPersonDetails, GetPersonDetailsResponse},
|
||||
utils::{check_private_instance, read_site_for_actor},
|
||||
};
|
||||
use lemmy_db_schema::source::person::Person;
|
||||
use lemmy_db_views::{
|
||||
profile_combined_view::ProfileCombinedQuery,
|
||||
structs::{LocalUserView, SiteView},
|
||||
};
|
||||
use lemmy_db_views::structs::{LocalUserView, SiteView};
|
||||
use lemmy_db_views_actor::structs::{CommunityModeratorView, PersonView};
|
||||
use lemmy_utils::error::{LemmyErrorType, LemmyResult};
|
||||
use lemmy_utils::error::LemmyResult;
|
||||
|
||||
#[tracing::instrument(skip(context))]
|
||||
pub async fn read_person(
|
||||
|
@ -20,65 +16,21 @@ pub async fn read_person(
|
|||
context: Data<LemmyContext>,
|
||||
local_user_view: Option<LocalUserView>,
|
||||
) -> LemmyResult<Json<GetPersonDetailsResponse>> {
|
||||
// Check to make sure a person name or an id is given
|
||||
if data.username.is_none() && data.person_id.is_none() {
|
||||
Err(LemmyErrorType::NoIdGiven)?
|
||||
}
|
||||
|
||||
let local_site = SiteView::read_local(&mut context.pool()).await?;
|
||||
|
||||
check_private_instance(&local_user_view, &local_site.local_site)?;
|
||||
|
||||
let person_details_id = match data.person_id {
|
||||
Some(id) => id,
|
||||
None => {
|
||||
if let Some(username) = &data.username {
|
||||
resolve_actor_identifier::<ApubPerson, Person>(username, &context, &local_user_view, true)
|
||||
.await?
|
||||
.id
|
||||
} else {
|
||||
Err(LemmyErrorType::NotFound)?
|
||||
}
|
||||
}
|
||||
};
|
||||
let person_details_id = resolve_person_id_from_id_or_username(
|
||||
&data.person_id,
|
||||
&data.username,
|
||||
&context,
|
||||
&local_user_view,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// You don't need to return settings for the user, since this comes back with GetSite
|
||||
// `my_user`
|
||||
let person_view = PersonView::read(&mut context.pool(), person_details_id).await?;
|
||||
|
||||
// parse pagination token
|
||||
let page_after = if let Some(pa) = &data.page_cursor {
|
||||
Some(pa.read(&mut context.pool()).await?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let page_back = data.page_back;
|
||||
let saved_only = data.saved_only;
|
||||
let community_id = data.community_id;
|
||||
|
||||
// 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 content = if let Some(creator_id) = creator_id {
|
||||
ProfileCombinedQuery {
|
||||
creator_id,
|
||||
community_id,
|
||||
saved_only,
|
||||
page_after,
|
||||
page_back,
|
||||
}
|
||||
.list(&mut context.pool(), &local_user_view)
|
||||
.await?
|
||||
} else {
|
||||
// if the creator is missing (saved_only, and no local_user), then return empty content
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
let moderates = CommunityModeratorView::for_person(
|
||||
&mut context.pool(),
|
||||
person_details_id,
|
||||
|
@ -92,6 +44,5 @@ pub async fn read_person(
|
|||
person_view,
|
||||
site,
|
||||
moderates,
|
||||
content,
|
||||
}))
|
||||
}
|
||||
|
|
|
@ -53,7 +53,6 @@ pub async fn search(
|
|||
limit,
|
||||
title_only,
|
||||
post_url_only,
|
||||
saved_only,
|
||||
liked_only,
|
||||
disliked_only,
|
||||
}) = data;
|
||||
|
@ -86,7 +85,6 @@ pub async fn search(
|
|||
url_only: post_url_only,
|
||||
liked_only,
|
||||
disliked_only,
|
||||
saved_only,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
|
@ -101,7 +99,6 @@ pub async fn search(
|
|||
limit,
|
||||
liked_only,
|
||||
disliked_only,
|
||||
saved_only,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
|
|
|
@ -685,31 +685,62 @@ CALL r.create_report_combined_trigger ('comment_report');
|
|||
|
||||
CALL r.create_report_combined_trigger ('private_message_report');
|
||||
|
||||
-- Profile (comment, post)
|
||||
CREATE PROCEDURE r.create_profile_combined_trigger (table_name text)
|
||||
-- person_content (comment, post)
|
||||
CREATE PROCEDURE r.create_person_content_combined_trigger (table_name text)
|
||||
LANGUAGE plpgsql
|
||||
AS $a$
|
||||
BEGIN
|
||||
EXECUTE replace($b$ CREATE FUNCTION r.profile_combined_thing_insert ( )
|
||||
EXECUTE replace($b$ CREATE FUNCTION r.person_content_combined_thing_insert ( )
|
||||
RETURNS TRIGGER
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
INSERT INTO profile_combined (published, thing_id)
|
||||
INSERT INTO person_content_combined (published, thing_id)
|
||||
VALUES (NEW.published, NEW.id);
|
||||
RETURN NEW;
|
||||
END $$;
|
||||
CREATE TRIGGER profile_combined
|
||||
CREATE TRIGGER person_content_combined
|
||||
AFTER INSERT ON thing
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION r.profile_combined_thing_insert ( );
|
||||
EXECUTE FUNCTION r.person_content_combined_thing_insert ( );
|
||||
$b$,
|
||||
'thing',
|
||||
table_name);
|
||||
END;
|
||||
$a$;
|
||||
|
||||
CALL r.create_profile_combined_trigger ('post');
|
||||
CALL r.create_person_content_combined_trigger ('post');
|
||||
|
||||
CALL r.create_person_content_combined_trigger ('comment');
|
||||
|
||||
-- person_saved (comment, post)
|
||||
-- TODO, not sure how to handle changes to post_actions and comment_actions.saved column.
|
||||
-- False should delete this row, true should insert
|
||||
-- CREATE PROCEDURE r.create_person_saved_combined_trigger (table_name text)
|
||||
-- LANGUAGE plpgsql
|
||||
-- AS $a$
|
||||
-- BEGIN
|
||||
-- EXECUTE replace($b$ CREATE FUNCTION r.person_saved_combined_thing_insert ( )
|
||||
-- RETURNS TRIGGER
|
||||
-- LANGUAGE plpgsql
|
||||
-- AS $$
|
||||
-- BEGIN
|
||||
-- INSERT INTO person_saved_combined (published, thing_id)
|
||||
-- VALUES (NEW.saved, NEW.id);
|
||||
-- RETURN NEW;
|
||||
-- END $$;
|
||||
-- CREATE TRIGGER person_saved_combined
|
||||
-- AFTER INSERT ON thing
|
||||
-- FOR EACH ROW
|
||||
-- EXECUTE FUNCTION r.person_saved_combined_thing_insert ( );
|
||||
-- $b$,
|
||||
-- 'thing',
|
||||
-- table_name);
|
||||
-- END;
|
||||
-- $a$;
|
||||
|
||||
-- CALL r.create_person_saved_combined_trigger ('post_actions');
|
||||
|
||||
-- CALL r.create_person_saved_combined_trigger ('comment_actions');
|
||||
|
||||
CALL r.create_profile_combined_trigger ('comment');
|
||||
|
||||
|
|
|
@ -188,8 +188,14 @@ pub struct ReportCombinedId(i32);
|
|||
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Default)]
|
||||
#[cfg_attr(feature = "full", derive(DieselNewType, TS))]
|
||||
#[cfg_attr(feature = "full", ts(export))]
|
||||
/// The profile combined id
|
||||
pub struct ProfileCombinedId(i32);
|
||||
/// The person content combined id
|
||||
pub struct PersonContentCombinedId(i32);
|
||||
|
||||
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Default)]
|
||||
#[cfg_attr(feature = "full", derive(DieselNewType, TS))]
|
||||
#[cfg_attr(feature = "full", ts(export))]
|
||||
/// The person saved combined id
|
||||
pub struct PersonSavedCombinedId(i32);
|
||||
|
||||
impl DbUrl {
|
||||
pub fn inner(&self) -> &Url {
|
||||
|
|
|
@ -729,6 +729,15 @@ diesel::table! {
|
|||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
person_content_combined (id) {
|
||||
id -> Int4,
|
||||
published -> Timestamptz,
|
||||
post_id -> Nullable<Int4>,
|
||||
comment_id -> Nullable<Int4>,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
person_mention (id) {
|
||||
id -> Int4,
|
||||
|
@ -739,6 +748,15 @@ diesel::table! {
|
|||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
person_saved_combined (id) {
|
||||
id -> Int4,
|
||||
published -> Timestamptz,
|
||||
post_id -> Nullable<Int4>,
|
||||
comment_id -> Nullable<Int4>,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
post (id) {
|
||||
id -> Int4,
|
||||
|
@ -856,15 +874,6 @@ diesel::table! {
|
|||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
profile_combined (id) {
|
||||
id -> Int4,
|
||||
published -> Timestamptz,
|
||||
post_id -> Nullable<Int4>,
|
||||
comment_id -> Nullable<Int4>,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
received_activity (ap_id) {
|
||||
ap_id -> Text,
|
||||
|
@ -1039,8 +1048,12 @@ diesel::joinable!(password_reset_request -> local_user (local_user_id));
|
|||
diesel::joinable!(person -> instance (instance_id));
|
||||
diesel::joinable!(person_aggregates -> person (person_id));
|
||||
diesel::joinable!(person_ban -> person (person_id));
|
||||
diesel::joinable!(person_content_combined -> comment (comment_id));
|
||||
diesel::joinable!(person_content_combined -> post (post_id));
|
||||
diesel::joinable!(person_mention -> comment (comment_id));
|
||||
diesel::joinable!(person_mention -> person (recipient_id));
|
||||
diesel::joinable!(person_saved_combined -> comment (comment_id));
|
||||
diesel::joinable!(person_saved_combined -> post (post_id));
|
||||
diesel::joinable!(post -> community (community_id));
|
||||
diesel::joinable!(post -> language (language_id));
|
||||
diesel::joinable!(post -> person (creator_id));
|
||||
|
@ -1052,8 +1065,6 @@ diesel::joinable!(post_aggregates -> person (creator_id));
|
|||
diesel::joinable!(post_aggregates -> post (post_id));
|
||||
diesel::joinable!(post_report -> post (post_id));
|
||||
diesel::joinable!(private_message_report -> private_message (private_message_id));
|
||||
diesel::joinable!(profile_combined -> comment (comment_id));
|
||||
diesel::joinable!(profile_combined -> post (post_id));
|
||||
diesel::joinable!(registration_application -> local_user (local_user_id));
|
||||
diesel::joinable!(registration_application -> person (admin_id));
|
||||
diesel::joinable!(report_combined -> comment_report (comment_report_id));
|
||||
|
@ -1117,14 +1128,15 @@ diesel::allow_tables_to_appear_in_same_query!(
|
|||
person_actions,
|
||||
person_aggregates,
|
||||
person_ban,
|
||||
person_content_combined,
|
||||
person_mention,
|
||||
person_saved_combined,
|
||||
post,
|
||||
post_actions,
|
||||
post_aggregates,
|
||||
post_report,
|
||||
private_message,
|
||||
private_message_report,
|
||||
profile_combined,
|
||||
received_activity,
|
||||
registration_application,
|
||||
remote_image,
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
pub mod profile;
|
||||
pub mod person_content;
|
||||
pub mod person_saved;
|
||||
pub mod report;
|
||||
|
|
30
crates/db_schema/src/source/combined/person_content.rs
Normal file
30
crates/db_schema/src/source/combined/person_content.rs
Normal file
|
@ -0,0 +1,30 @@
|
|||
use crate::newtypes::{CommentId, PersonContentCombinedId, PostId};
|
||||
#[cfg(feature = "full")]
|
||||
use crate::schema::person_content_combined;
|
||||
use chrono::{DateTime, Utc};
|
||||
#[cfg(feature = "full")]
|
||||
use i_love_jesus::CursorKeysModule;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::skip_serializing_none;
|
||||
#[cfg(feature = "full")]
|
||||
use ts_rs::TS;
|
||||
|
||||
#[skip_serializing_none]
|
||||
#[derive(PartialEq, Eq, Serialize, Deserialize, Debug, Clone)]
|
||||
#[cfg_attr(
|
||||
feature = "full",
|
||||
derive(Identifiable, Queryable, Selectable, TS, CursorKeysModule)
|
||||
)]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = person_content_combined))]
|
||||
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
|
||||
#[cfg_attr(feature = "full", ts(export))]
|
||||
#[cfg_attr(feature = "full", cursor_keys_module(name = person_content_combined_keys))]
|
||||
/// A combined table for a persons contents (posts and comments)
|
||||
pub struct PersonContentCombined {
|
||||
pub id: PersonContentCombinedId,
|
||||
pub published: DateTime<Utc>,
|
||||
#[cfg_attr(feature = "full", ts(optional))]
|
||||
pub post_id: Option<PostId>,
|
||||
#[cfg_attr(feature = "full", ts(optional))]
|
||||
pub comment_id: Option<CommentId>,
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
use crate::newtypes::{CommentId, PostId, ProfileCombinedId};
|
||||
use crate::newtypes::{CommentId, PersonSavedCombinedId, PostId};
|
||||
#[cfg(feature = "full")]
|
||||
use crate::schema::profile_combined;
|
||||
use crate::schema::person_saved_combined;
|
||||
use chrono::{DateTime, Utc};
|
||||
#[cfg(feature = "full")]
|
||||
use i_love_jesus::CursorKeysModule;
|
||||
|
@ -15,13 +15,13 @@ use ts_rs::TS;
|
|||
feature = "full",
|
||||
derive(Identifiable, Queryable, Selectable, TS, CursorKeysModule)
|
||||
)]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = profile_combined))]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = person_saved_combined))]
|
||||
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
|
||||
#[cfg_attr(feature = "full", ts(export))]
|
||||
#[cfg_attr(feature = "full", cursor_keys_module(name = profile_combined_keys))]
|
||||
/// A combined profile table.
|
||||
pub struct ProfileCombined {
|
||||
pub id: ProfileCombinedId,
|
||||
#[cfg_attr(feature = "full", cursor_keys_module(name = person_saved_combined_keys))]
|
||||
/// A combined person_saved table.
|
||||
pub struct PersonSavedCombined {
|
||||
pub id: PersonSavedCombinedId,
|
||||
pub published: DateTime<Utc>,
|
||||
#[cfg_attr(feature = "full", ts(optional))]
|
||||
pub post_id: Option<PostId>,
|
|
@ -40,6 +40,7 @@ ts-rs = { workspace = true, optional = true }
|
|||
actix-web = { workspace = true, optional = true }
|
||||
i-love-jesus = { workspace = true, optional = true }
|
||||
chrono = { workspace = true }
|
||||
derive-new.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
serial_test = { workspace = true }
|
||||
|
|
|
@ -189,13 +189,6 @@ fn queries<'a>() -> Queries<
|
|||
}
|
||||
}
|
||||
|
||||
// If its saved only, then filter, and order by the saved time, not the comment creation time.
|
||||
if options.saved_only.unwrap_or_default() {
|
||||
query = query
|
||||
.filter(comment_actions::saved.is_not_null())
|
||||
.then_order_by(comment_actions::saved.desc());
|
||||
}
|
||||
|
||||
if let Some(my_id) = options.local_user.person_id() {
|
||||
let not_creator_filter = comment::creator_id.ne(my_id);
|
||||
if options.liked_only.unwrap_or_default() {
|
||||
|
@ -337,7 +330,6 @@ pub struct CommentQuery<'a> {
|
|||
pub creator_id: Option<PersonId>,
|
||||
pub local_user: Option<&'a LocalUser>,
|
||||
pub search_term: Option<String>,
|
||||
pub saved_only: Option<bool>,
|
||||
pub liked_only: Option<bool>,
|
||||
pub disliked_only: Option<bool>,
|
||||
pub page: Option<i64>,
|
||||
|
@ -381,15 +373,7 @@ mod tests {
|
|||
newtypes::LanguageId,
|
||||
source::{
|
||||
actor_language::LocalUserLanguage,
|
||||
comment::{
|
||||
Comment,
|
||||
CommentInsertForm,
|
||||
CommentLike,
|
||||
CommentLikeForm,
|
||||
CommentSaved,
|
||||
CommentSavedForm,
|
||||
CommentUpdateForm,
|
||||
},
|
||||
comment::{Comment, CommentInsertForm, CommentLike, CommentLikeForm, CommentUpdateForm},
|
||||
community::{
|
||||
Community,
|
||||
CommunityFollower,
|
||||
|
@ -411,7 +395,7 @@ mod tests {
|
|||
post::{Post, PostInsertForm, PostUpdateForm},
|
||||
site::{Site, SiteInsertForm},
|
||||
},
|
||||
traits::{Bannable, Blockable, Crud, Followable, Joinable, Likeable, Saveable},
|
||||
traits::{Bannable, Blockable, Crud, Followable, Joinable, Likeable},
|
||||
utils::{build_db_pool_for_tests, RANK_DEFAULT},
|
||||
CommunityVisibility,
|
||||
SubscribedType,
|
||||
|
@ -897,47 +881,6 @@ mod tests {
|
|||
cleanup(data, pool).await
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_saved_order() -> LemmyResult<()> {
|
||||
let pool = &build_db_pool_for_tests();
|
||||
let pool = &mut pool.into();
|
||||
let data = init_data(pool).await?;
|
||||
|
||||
// Save two comments
|
||||
let save_comment_0_form = CommentSavedForm {
|
||||
person_id: data.timmy_local_user_view.person.id,
|
||||
comment_id: data.inserted_comment_0.id,
|
||||
};
|
||||
CommentSaved::save(pool, &save_comment_0_form).await?;
|
||||
|
||||
let save_comment_2_form = CommentSavedForm {
|
||||
person_id: data.timmy_local_user_view.person.id,
|
||||
comment_id: data.inserted_comment_2.id,
|
||||
};
|
||||
CommentSaved::save(pool, &save_comment_2_form).await?;
|
||||
|
||||
// Fetch the saved comments
|
||||
let comments = CommentQuery {
|
||||
local_user: Some(&data.timmy_local_user_view.local_user),
|
||||
saved_only: Some(true),
|
||||
..Default::default()
|
||||
}
|
||||
.list(&data.site, pool)
|
||||
.await?;
|
||||
|
||||
// There should only be two comments
|
||||
assert_eq!(2, comments.len());
|
||||
|
||||
// The first comment, should be the last one saved (descending order)
|
||||
assert_eq!(comments[0].comment.id, data.inserted_comment_2.id);
|
||||
|
||||
// The second comment, should be the first one saved
|
||||
assert_eq!(comments[1].comment.id, data.inserted_comment_0.id);
|
||||
|
||||
cleanup(data, pool).await
|
||||
}
|
||||
|
||||
async fn cleanup(data: Data, pool: &mut DbPool<'_>) -> LemmyResult<()> {
|
||||
CommentLike::remove(
|
||||
pool,
|
||||
|
|
|
@ -12,6 +12,10 @@ pub mod local_image_view;
|
|||
#[cfg(feature = "full")]
|
||||
pub mod local_user_view;
|
||||
#[cfg(feature = "full")]
|
||||
pub mod person_content_combined_view;
|
||||
#[cfg(feature = "full")]
|
||||
pub mod person_saved_combined_view;
|
||||
#[cfg(feature = "full")]
|
||||
pub mod post_report_view;
|
||||
#[cfg(feature = "full")]
|
||||
pub mod post_view;
|
||||
|
@ -20,8 +24,6 @@ pub mod private_message_report_view;
|
|||
#[cfg(feature = "full")]
|
||||
pub mod private_message_view;
|
||||
#[cfg(feature = "full")]
|
||||
pub mod profile_combined_view;
|
||||
#[cfg(feature = "full")]
|
||||
pub mod registration_application_view;
|
||||
#[cfg(feature = "full")]
|
||||
pub mod report_combined_view;
|
||||
|
@ -30,3 +32,10 @@ pub mod site_view;
|
|||
pub mod structs;
|
||||
#[cfg(feature = "full")]
|
||||
pub mod vote_view;
|
||||
|
||||
pub trait InternalToCombinedView {
|
||||
type CombinedView;
|
||||
|
||||
/// Maps the combined DB row to an enum
|
||||
fn map_to_enum(&self) -> Option<Self::CombinedView>;
|
||||
}
|
||||
|
|
430
crates/db_views/src/person_content_combined_view.rs
Normal file
430
crates/db_views/src/person_content_combined_view.rs
Normal file
|
@ -0,0 +1,430 @@
|
|||
use crate::{
|
||||
structs::{
|
||||
CommentView,
|
||||
LocalUserView,
|
||||
PersonContentCombinedPaginationCursor,
|
||||
PersonContentCombinedView,
|
||||
PersonContentViewInternal,
|
||||
PostView,
|
||||
},
|
||||
InternalToCombinedView,
|
||||
};
|
||||
use diesel::{
|
||||
result::Error,
|
||||
BoolExpressionMethods,
|
||||
ExpressionMethods,
|
||||
JoinOnDsl,
|
||||
NullableExpressionMethods,
|
||||
QueryDsl,
|
||||
SelectableHelper,
|
||||
};
|
||||
use diesel_async::RunQueryDsl;
|
||||
use i_love_jesus::PaginatedQueryBuilder;
|
||||
use lemmy_db_schema::{
|
||||
aliases::creator_community_actions,
|
||||
newtypes::PersonId,
|
||||
schema::{
|
||||
comment,
|
||||
comment_actions,
|
||||
comment_aggregates,
|
||||
community,
|
||||
community_actions,
|
||||
image_details,
|
||||
local_user,
|
||||
person,
|
||||
person_actions,
|
||||
person_content_combined,
|
||||
post,
|
||||
post_actions,
|
||||
post_aggregates,
|
||||
},
|
||||
source::{
|
||||
combined::person_content::{person_content_combined_keys as key, PersonContentCombined},
|
||||
community::CommunityFollower,
|
||||
},
|
||||
utils::{actions, actions_alias, functions::coalesce, get_conn, DbPool},
|
||||
};
|
||||
use lemmy_utils::error::LemmyResult;
|
||||
|
||||
impl PersonContentCombinedPaginationCursor {
|
||||
// get cursor for page that starts immediately after the given post
|
||||
pub fn after_post(view: &PersonContentCombinedView) -> PersonContentCombinedPaginationCursor {
|
||||
let (prefix, id) = match view {
|
||||
PersonContentCombinedView::Comment(v) => ('C', v.comment.id.0),
|
||||
PersonContentCombinedView::Post(v) => ('P', v.post.id.0),
|
||||
};
|
||||
// hex encoding to prevent ossification
|
||||
PersonContentCombinedPaginationCursor(format!("{prefix}{id:x}"))
|
||||
}
|
||||
|
||||
pub async fn read(&self, pool: &mut DbPool<'_>) -> Result<PaginationCursorData, Error> {
|
||||
let err_msg = || Error::QueryBuilderError("Could not parse pagination token".into());
|
||||
let mut query = person_content_combined::table
|
||||
.select(PersonContentCombined::as_select())
|
||||
.into_boxed();
|
||||
let (prefix, id_str) = self.0.split_at_checked(1).ok_or_else(err_msg)?;
|
||||
let id = i32::from_str_radix(id_str, 16).map_err(|_err| err_msg())?;
|
||||
query = match prefix {
|
||||
"C" => query.filter(person_content_combined::comment_id.eq(id)),
|
||||
"P" => query.filter(person_content_combined::post_id.eq(id)),
|
||||
_ => return Err(err_msg()),
|
||||
};
|
||||
let token = query.first(&mut get_conn(pool).await?).await?;
|
||||
|
||||
Ok(PaginationCursorData(token))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct PaginationCursorData(PersonContentCombined);
|
||||
|
||||
#[derive(derive_new::new)]
|
||||
pub struct PersonContentCombinedQuery {
|
||||
pub creator_id: PersonId,
|
||||
#[new(default)]
|
||||
pub page_after: Option<PaginationCursorData>,
|
||||
#[new(default)]
|
||||
pub page_back: Option<bool>,
|
||||
}
|
||||
|
||||
impl PersonContentCombinedQuery {
|
||||
pub async fn list(
|
||||
self,
|
||||
pool: &mut DbPool<'_>,
|
||||
user: &Option<LocalUserView>,
|
||||
) -> LemmyResult<Vec<PersonContentCombinedView>> {
|
||||
let my_person_id = user.as_ref().map(|u| u.local_user.person_id);
|
||||
let item_creator = person::id;
|
||||
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
|
||||
// Notes: since the post_id and comment_id are optional columns,
|
||||
// many joins must use an OR condition.
|
||||
// For example, the creator must be the person table joined to either:
|
||||
// - post.creator_id
|
||||
// - comment.creator_id
|
||||
let query = person_content_combined::table
|
||||
// The comment
|
||||
.left_join(comment::table.on(person_content_combined::comment_id.eq(comment::id.nullable())))
|
||||
// The post
|
||||
// It gets a bit complicated here, because since both comments and post combined have a post
|
||||
// attached, you can do an inner join.
|
||||
.inner_join(
|
||||
post::table.on(
|
||||
person_content_combined::post_id
|
||||
.eq(post::id.nullable())
|
||||
.or(comment::post_id.eq(post::id)),
|
||||
),
|
||||
)
|
||||
// The item creator
|
||||
.inner_join(
|
||||
person::table.on(
|
||||
comment::creator_id
|
||||
.eq(item_creator)
|
||||
// Need to filter out the post rows where both the post and comment creator are the
|
||||
// same.
|
||||
.or(
|
||||
post::creator_id
|
||||
.eq(item_creator)
|
||||
.and(person_content_combined::post_id.is_not_null()),
|
||||
),
|
||||
),
|
||||
)
|
||||
// The community
|
||||
.inner_join(community::table.on(post::community_id.eq(community::id)))
|
||||
.left_join(actions_alias(
|
||||
creator_community_actions,
|
||||
item_creator,
|
||||
post::community_id,
|
||||
))
|
||||
.left_join(
|
||||
local_user::table.on(
|
||||
item_creator
|
||||
.eq(local_user::person_id)
|
||||
.and(local_user::admin.eq(true)),
|
||||
),
|
||||
)
|
||||
.left_join(actions(
|
||||
community_actions::table,
|
||||
my_person_id,
|
||||
post::community_id,
|
||||
))
|
||||
.left_join(actions(post_actions::table, my_person_id, post::id))
|
||||
.left_join(actions(person_actions::table, my_person_id, item_creator))
|
||||
.inner_join(post_aggregates::table.on(post::id.eq(post_aggregates::post_id)))
|
||||
.left_join(
|
||||
comment_aggregates::table
|
||||
.on(person_content_combined::comment_id.eq(comment_aggregates::comment_id.nullable())),
|
||||
)
|
||||
.left_join(actions(comment_actions::table, my_person_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((
|
||||
// Post-specific
|
||||
post_aggregates::all_columns,
|
||||
coalesce(
|
||||
post_aggregates::comments.nullable() - post_actions::read_comments_amount.nullable(),
|
||||
post_aggregates::comments,
|
||||
),
|
||||
post_actions::saved.nullable().is_not_null(),
|
||||
post_actions::read.nullable().is_not_null(),
|
||||
post_actions::hidden.nullable().is_not_null(),
|
||||
post_actions::like_score.nullable(),
|
||||
image_details::all_columns.nullable(),
|
||||
// Comment-specific
|
||||
comment::all_columns.nullable(),
|
||||
comment_aggregates::all_columns.nullable(),
|
||||
comment_actions::saved.nullable().is_not_null(),
|
||||
comment_actions::like_score.nullable(),
|
||||
// Shared
|
||||
post::all_columns,
|
||||
community::all_columns,
|
||||
person::all_columns,
|
||||
CommunityFollower::select_subscribed_type(),
|
||||
local_user::admin.nullable().is_not_null(),
|
||||
creator_community_actions
|
||||
.field(community_actions::became_moderator)
|
||||
.nullable()
|
||||
.is_not_null(),
|
||||
creator_community_actions
|
||||
.field(community_actions::received_ban)
|
||||
.nullable()
|
||||
.is_not_null(),
|
||||
person_actions::blocked.nullable().is_not_null(),
|
||||
community_actions::received_ban.nullable().is_not_null(),
|
||||
))
|
||||
.into_boxed();
|
||||
|
||||
let mut query = PaginatedQueryBuilder::new(query);
|
||||
|
||||
let page_after = self.page_after.map(|c| c.0);
|
||||
|
||||
if self.page_back.unwrap_or_default() {
|
||||
query = query.before(page_after).limit_and_offset_from_end();
|
||||
} else {
|
||||
query = query.after(page_after);
|
||||
}
|
||||
|
||||
// Sorting by published
|
||||
query = query
|
||||
.then_desc(key::published)
|
||||
// Tie breaker
|
||||
.then_desc(key::id);
|
||||
|
||||
let res = query.load::<PersonContentViewInternal>(conn).await?;
|
||||
|
||||
// Map the query results to the enum
|
||||
let out = res.into_iter().filter_map(|u| u.map_to_enum()).collect();
|
||||
|
||||
Ok(out)
|
||||
}
|
||||
}
|
||||
|
||||
impl InternalToCombinedView for PersonContentViewInternal {
|
||||
type CombinedView = PersonContentCombinedView;
|
||||
|
||||
fn map_to_enum(&self) -> Option<Self::CombinedView> {
|
||||
// Use for a short alias
|
||||
let v = self.clone();
|
||||
|
||||
if let (Some(comment), Some(counts)) = (v.comment, v.comment_counts) {
|
||||
Some(PersonContentCombinedView::Comment(CommentView {
|
||||
comment,
|
||||
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(PersonContentCombinedView::Post(PostView {
|
||||
post: v.post,
|
||||
community: v.community,
|
||||
unread_comments: v.post_unread_comments,
|
||||
counts: v.post_counts,
|
||||
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.post_saved,
|
||||
read: v.post_read,
|
||||
hidden: v.post_hidden,
|
||||
my_vote: v.my_post_vote,
|
||||
image_details: v.image_details,
|
||||
banned_from_community: v.banned_from_community,
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[expect(clippy::indexing_slicing)]
|
||||
mod tests {
|
||||
|
||||
use crate::{
|
||||
person_content_combined_view::PersonContentCombinedQuery,
|
||||
structs::PersonContentCombinedView,
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
source::{
|
||||
comment::{Comment, CommentInsertForm},
|
||||
community::{Community, CommunityInsertForm},
|
||||
instance::Instance,
|
||||
person::{Person, PersonInsertForm},
|
||||
post::{Post, PostInsertForm},
|
||||
},
|
||||
traits::Crud,
|
||||
utils::{build_db_pool_for_tests, DbPool},
|
||||
};
|
||||
use lemmy_utils::error::LemmyResult;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serial_test::serial;
|
||||
|
||||
struct Data {
|
||||
instance: Instance,
|
||||
timmy: Person,
|
||||
sara: Person,
|
||||
timmy_post: Post,
|
||||
timmy_post_2: Post,
|
||||
sara_post: Post,
|
||||
timmy_comment: Comment,
|
||||
sara_comment: Comment,
|
||||
sara_comment_2: Comment,
|
||||
}
|
||||
|
||||
async fn init_data(pool: &mut DbPool<'_>) -> LemmyResult<Data> {
|
||||
let instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?;
|
||||
|
||||
let timmy_form = PersonInsertForm::test_form(instance.id, "timmy_pcv");
|
||||
let timmy = Person::create(pool, &timmy_form).await?;
|
||||
|
||||
let sara_form = PersonInsertForm::test_form(instance.id, "sara_pcv");
|
||||
let sara = Person::create(pool, &sara_form).await?;
|
||||
|
||||
let community_form = CommunityInsertForm::new(
|
||||
instance.id,
|
||||
"test community pcv".to_string(),
|
||||
"nada".to_owned(),
|
||||
"pubkey".to_string(),
|
||||
);
|
||||
let community = Community::create(pool, &community_form).await?;
|
||||
|
||||
let timmy_post_form = PostInsertForm::new("timmy post prv".into(), timmy.id, community.id);
|
||||
let timmy_post = Post::create(pool, &timmy_post_form).await?;
|
||||
|
||||
let timmy_post_form_2 = PostInsertForm::new("timmy post prv 2".into(), timmy.id, community.id);
|
||||
let timmy_post_2 = Post::create(pool, &timmy_post_form_2).await?;
|
||||
|
||||
let sara_post_form = PostInsertForm::new("sara post prv".into(), sara.id, community.id);
|
||||
let sara_post = Post::create(pool, &sara_post_form).await?;
|
||||
|
||||
let timmy_comment_form =
|
||||
CommentInsertForm::new(timmy.id, timmy_post.id, "timmy comment prv".into());
|
||||
let timmy_comment = Comment::create(pool, &timmy_comment_form, None).await?;
|
||||
|
||||
let sara_comment_form =
|
||||
CommentInsertForm::new(sara.id, timmy_post.id, "sara comment prv".into());
|
||||
let sara_comment = Comment::create(pool, &sara_comment_form, None).await?;
|
||||
|
||||
let sara_comment_form_2 =
|
||||
CommentInsertForm::new(sara.id, timmy_post_2.id, "sara comment prv 2".into());
|
||||
let sara_comment_2 = Comment::create(pool, &sara_comment_form_2, None).await?;
|
||||
|
||||
Ok(Data {
|
||||
instance,
|
||||
timmy,
|
||||
sara,
|
||||
timmy_post,
|
||||
timmy_post_2,
|
||||
sara_post,
|
||||
timmy_comment,
|
||||
sara_comment,
|
||||
sara_comment_2,
|
||||
})
|
||||
}
|
||||
|
||||
async fn cleanup(data: Data, pool: &mut DbPool<'_>) -> LemmyResult<()> {
|
||||
Instance::delete(pool, data.instance.id).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_combined() -> LemmyResult<()> {
|
||||
let pool = &build_db_pool_for_tests();
|
||||
let pool = &mut pool.into();
|
||||
let data = init_data(pool).await?;
|
||||
|
||||
// Do a batch read of timmy
|
||||
let timmy_content = PersonContentCombinedQuery::new(data.timmy.id)
|
||||
.list(pool, &None)
|
||||
.await?;
|
||||
assert_eq!(3, timmy_content.len());
|
||||
|
||||
// Make sure the report types are correct
|
||||
if let PersonContentCombinedView::Comment(v) = &timmy_content[0] {
|
||||
assert_eq!(data.timmy_comment.id, v.comment.id);
|
||||
assert_eq!(data.timmy.id, v.creator.id);
|
||||
} else {
|
||||
panic!("wrong type");
|
||||
}
|
||||
if let PersonContentCombinedView::Post(v) = &timmy_content[1] {
|
||||
assert_eq!(data.timmy_post_2.id, v.post.id);
|
||||
assert_eq!(data.timmy.id, v.post.creator_id);
|
||||
} else {
|
||||
panic!("wrong type");
|
||||
}
|
||||
if let PersonContentCombinedView::Post(v) = &timmy_content[2] {
|
||||
assert_eq!(data.timmy_post.id, v.post.id);
|
||||
assert_eq!(data.timmy.id, v.post.creator_id);
|
||||
} else {
|
||||
panic!("wrong type");
|
||||
}
|
||||
|
||||
// Do a batch read of sara
|
||||
let sara_content = PersonContentCombinedQuery::new(data.sara.id)
|
||||
.list(pool, &None)
|
||||
.await?;
|
||||
assert_eq!(3, sara_content.len());
|
||||
|
||||
// Make sure the report types are correct
|
||||
if let PersonContentCombinedView::Comment(v) = &sara_content[0] {
|
||||
assert_eq!(data.sara_comment_2.id, v.comment.id);
|
||||
assert_eq!(data.sara.id, v.creator.id);
|
||||
// This one was to timmy_post_2
|
||||
assert_eq!(data.timmy_post_2.id, v.post.id);
|
||||
assert_eq!(data.timmy.id, v.post.creator_id);
|
||||
} else {
|
||||
panic!("wrong type");
|
||||
}
|
||||
if let PersonContentCombinedView::Comment(v) = &sara_content[1] {
|
||||
assert_eq!(data.sara_comment.id, v.comment.id);
|
||||
assert_eq!(data.sara.id, v.creator.id);
|
||||
assert_eq!(data.timmy_post.id, v.post.id);
|
||||
assert_eq!(data.timmy.id, v.post.creator_id);
|
||||
} else {
|
||||
panic!("wrong type");
|
||||
}
|
||||
if let PersonContentCombinedView::Post(v) = &sara_content[2] {
|
||||
assert_eq!(data.sara_post.id, v.post.id);
|
||||
assert_eq!(data.sara.id, v.post.creator_id);
|
||||
} else {
|
||||
panic!("wrong type");
|
||||
}
|
||||
|
||||
cleanup(data, pool).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
554
crates/db_views/src/person_saved_combined_view.rs
Normal file
554
crates/db_views/src/person_saved_combined_view.rs
Normal file
|
@ -0,0 +1,554 @@
|
|||
// use crate::{
|
||||
// structs::{
|
||||
// CommentView,
|
||||
// LocalUserView,
|
||||
// PostView,
|
||||
// ProfileCombinedPaginationCursor,
|
||||
// PersonContentCombinedView,
|
||||
// PersonContentViewInternal,
|
||||
// },
|
||||
// InternalToCombinedView,
|
||||
// };
|
||||
// use diesel::{
|
||||
// result::Error,
|
||||
// BoolExpressionMethods,
|
||||
// ExpressionMethods,
|
||||
// JoinOnDsl,
|
||||
// NullableExpressionMethods,
|
||||
// QueryDsl,
|
||||
// SelectableHelper,
|
||||
// };
|
||||
// use diesel_async::RunQueryDsl;
|
||||
// use i_love_jesus::PaginatedQueryBuilder;
|
||||
// use lemmy_db_schema::{
|
||||
// aliases::creator_community_actions,
|
||||
// newtypes::{CommunityId, PersonId},
|
||||
// schema::{
|
||||
// comment,
|
||||
// comment_actions,
|
||||
// comment_aggregates,
|
||||
// community,
|
||||
// community_actions,
|
||||
// image_details,
|
||||
// local_user,
|
||||
// person,
|
||||
// person_actions,
|
||||
// post,
|
||||
// post_actions,
|
||||
// post_aggregates,
|
||||
// profile_combined,
|
||||
// },
|
||||
// source::{
|
||||
// combined::profile::{profile_combined_keys as key, ProfileCombined},
|
||||
// community::CommunityFollower,
|
||||
// },
|
||||
// utils::{actions, actions_alias, functions::coalesce, get_conn, DbPool, ReverseTimestampKey},
|
||||
// };
|
||||
// use lemmy_utils::error::LemmyResult;
|
||||
|
||||
// impl ProfileCombinedPaginationCursor {
|
||||
// // get cursor for page that starts immediately after the given post
|
||||
// pub fn after_post(view: &PersonContentCombinedView) -> ProfileCombinedPaginationCursor {
|
||||
// let (prefix, id) = match view {
|
||||
// PersonContentCombinedView::Comment(v) => ('C', v.comment.id.0),
|
||||
// PersonContentCombinedView::Post(v) => ('P', v.post.id.0),
|
||||
// };
|
||||
// // hex encoding to prevent ossification
|
||||
// ProfileCombinedPaginationCursor(format!("{prefix}{id:x}"))
|
||||
// }
|
||||
|
||||
// pub async fn read(&self, pool: &mut DbPool<'_>) -> Result<PaginationCursorData, Error> {
|
||||
// let err_msg = || Error::QueryBuilderError("Could not parse pagination token".into());
|
||||
// let mut query = profile_combined::table
|
||||
// .select(ProfileCombined::as_select())
|
||||
// .into_boxed();
|
||||
// let (prefix, id_str) = self.0.split_at_checked(1).ok_or_else(err_msg)?;
|
||||
// let id = i32::from_str_radix(id_str, 16).map_err(|_err| err_msg())?;
|
||||
// query = match prefix {
|
||||
// "C" => query.filter(profile_combined::comment_id.eq(id)),
|
||||
// "P" => query.filter(profile_combined::post_id.eq(id)),
|
||||
// _ => return Err(err_msg()),
|
||||
// };
|
||||
// let token = query.first(&mut get_conn(pool).await?).await?;
|
||||
|
||||
// Ok(PaginationCursorData(token))
|
||||
// }
|
||||
// }
|
||||
|
||||
// #[derive(Clone)]
|
||||
// pub struct PaginationCursorData(ProfileCombined);
|
||||
|
||||
// #[derive(Default)]
|
||||
// pub struct ProfileCombinedQuery {
|
||||
// pub creator_id: PersonId,
|
||||
// pub page_after: Option<PaginationCursorData>,
|
||||
// pub page_back: Option<bool>,
|
||||
// }
|
||||
|
||||
// impl ProfileCombinedQuery {
|
||||
// pub async fn list(
|
||||
// self,
|
||||
// pool: &mut DbPool<'_>,
|
||||
// user: &Option<LocalUserView>,
|
||||
// ) -> LemmyResult<Vec<PersonContentCombinedView>> {
|
||||
// let my_person_id = user
|
||||
// .as_ref()
|
||||
// .map(|u| u.local_user.person_id)
|
||||
// .unwrap_or(PersonId(-1));
|
||||
// let item_creator = person::id;
|
||||
|
||||
// let conn = &mut get_conn(pool).await?;
|
||||
|
||||
// // Notes: since the post_id and comment_id are optional columns,
|
||||
// // many joins must use an OR condition.
|
||||
// // For example, the creator must be the person table joined to either:
|
||||
// // - post.creator_id
|
||||
// // - comment.creator_id
|
||||
// let mut query = profile_combined::table
|
||||
// // The comment
|
||||
// .left_join(comment::table.on(profile_combined::comment_id.eq(comment::id.nullable())))
|
||||
// // The post
|
||||
// .inner_join(
|
||||
// post::table.on(
|
||||
// profile_combined::post_id
|
||||
// .eq(post::id.nullable())
|
||||
// .or(comment::post_id.nullable().eq(profile_combined::post_id)),
|
||||
// ),
|
||||
// )
|
||||
// // The item creator
|
||||
// .inner_join(
|
||||
// person::table.on(
|
||||
// comment::creator_id
|
||||
// .eq(person::id)
|
||||
// .or(post::creator_id.eq(person::id)),
|
||||
// ),
|
||||
// )
|
||||
// // The community
|
||||
// .inner_join(community::table.on(post::community_id.eq(community::id)))
|
||||
// .left_join(actions_alias(
|
||||
// creator_community_actions,
|
||||
// item_creator,
|
||||
// post::community_id,
|
||||
// ))
|
||||
// .left_join(
|
||||
// local_user::table.on(
|
||||
// item_creator
|
||||
// .eq(local_user::person_id)
|
||||
// .and(local_user::admin.eq(true)),
|
||||
// ),
|
||||
// )
|
||||
// .left_join(actions(
|
||||
// community_actions::table,
|
||||
// Some(my_person_id),
|
||||
// post::community_id,
|
||||
// ))
|
||||
// .left_join(actions(post_actions::table, Some(my_person_id), post::id))
|
||||
// .left_join(actions(
|
||||
// person_actions::table,
|
||||
// Some(my_person_id),
|
||||
// item_creator,
|
||||
// ))
|
||||
// .inner_join(post_aggregates::table.on(post::id.eq(post_aggregates::post_id)))
|
||||
// .left_join(
|
||||
// comment_aggregates::table
|
||||
// .on(profile_combined::comment_id.eq(comment_aggregates::comment_id.nullable())),
|
||||
// )
|
||||
// .left_join(actions(
|
||||
// comment_actions::table,
|
||||
// Some(my_person_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((
|
||||
// // Post-specific
|
||||
// post_aggregates::all_columns,
|
||||
// coalesce(
|
||||
// post_aggregates::comments.nullable() - post_actions::read_comments_amount.nullable(),
|
||||
// post_aggregates::comments,
|
||||
// ),
|
||||
// post_actions::saved.nullable().is_not_null(),
|
||||
// post_actions::read.nullable().is_not_null(),
|
||||
// post_actions::hidden.nullable().is_not_null(),
|
||||
// post_actions::like_score.nullable(),
|
||||
// image_details::all_columns.nullable(),
|
||||
// // Comment-specific
|
||||
// comment::all_columns.nullable(),
|
||||
// comment_aggregates::all_columns.nullable(),
|
||||
// comment_actions::saved.nullable().is_not_null(),
|
||||
// comment_actions::like_score.nullable(),
|
||||
// // Shared
|
||||
// post::all_columns,
|
||||
// community::all_columns,
|
||||
// person::all_columns,
|
||||
// CommunityFollower::select_subscribed_type(),
|
||||
// local_user::admin.nullable().is_not_null(),
|
||||
// creator_community_actions
|
||||
// .field(community_actions::became_moderator)
|
||||
// .nullable()
|
||||
// .is_not_null(),
|
||||
// creator_community_actions
|
||||
// .field(community_actions::received_ban)
|
||||
// .nullable()
|
||||
// .is_not_null(),
|
||||
// person_actions::blocked.nullable().is_not_null(),
|
||||
// community_actions::received_ban.nullable().is_not_null(),
|
||||
// ))
|
||||
// .into_boxed();
|
||||
|
||||
// let mut query = PaginatedQueryBuilder::new(query);
|
||||
|
||||
// let page_after = self.page_after.map(|c| c.0);
|
||||
|
||||
// if self.page_back.unwrap_or_default() {
|
||||
// query = query.before(page_after).limit_and_offset_from_end();
|
||||
// } else {
|
||||
// query = query.after(page_after);
|
||||
// }
|
||||
|
||||
// // Sorting by published
|
||||
// query = query
|
||||
// .then_desc(ReverseTimestampKey(key::published))
|
||||
// // Tie breaker
|
||||
// .then_desc(key::id);
|
||||
|
||||
// let res = query.load::<PersonContentViewInternal>(conn).await?;
|
||||
|
||||
// // Map the query results to the enum
|
||||
// let out = res.into_iter().filter_map(|u| u.map_to_enum()).collect();
|
||||
|
||||
// Ok(out)
|
||||
// }
|
||||
// }
|
||||
|
||||
// impl InternalToCombinedView for PersonContentViewInternal {
|
||||
// type CombinedView = PersonContentCombinedView;
|
||||
|
||||
// fn map_to_enum(&self) -> Option<Self::CombinedView> {
|
||||
// // Use for a short alias
|
||||
// let v = self.clone();
|
||||
|
||||
// if let (Some(comment), Some(counts)) = (v.comment, v.comment_counts) {
|
||||
// Some(PersonContentCombinedView::Comment(CommentView {
|
||||
// comment,
|
||||
// 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(PersonContentCombinedView::Post(PostView {
|
||||
// post: v.post,
|
||||
// community: v.community,
|
||||
// unread_comments: v.post_unread_comments,
|
||||
// counts: v.post_counts,
|
||||
// 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.post_saved,
|
||||
// read: v.post_read,
|
||||
// hidden: v.post_hidden,
|
||||
// my_vote: v.my_post_vote,
|
||||
// image_details: v.image_details,
|
||||
// banned_from_community: v.banned_from_community,
|
||||
// }))
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// #[cfg(test)]
|
||||
// #[expect(clippy::indexing_slicing)]
|
||||
// mod tests {
|
||||
|
||||
// use crate::{
|
||||
// profile_combined_view::ProfileCombinedQuery,
|
||||
// report_combined_view::ReportCombinedQuery,
|
||||
// structs::{
|
||||
// CommentReportView,
|
||||
// LocalUserView,
|
||||
// PostReportView,
|
||||
// PersonContentCombinedView,
|
||||
// ReportCombinedView,
|
||||
// ReportCombinedViewInternal,
|
||||
// },
|
||||
// };
|
||||
// use lemmy_db_schema::{
|
||||
// aggregates::structs::{CommentAggregates, PostAggregates},
|
||||
// assert_length,
|
||||
// source::{
|
||||
// comment::{Comment, CommentInsertForm, CommentSaved, CommentSavedForm},
|
||||
// comment_report::{CommentReport, CommentReportForm},
|
||||
// community::{Community, CommunityInsertForm, CommunityModerator, CommunityModeratorForm},
|
||||
// instance::Instance,
|
||||
// local_user::{LocalUser, LocalUserInsertForm},
|
||||
// local_user_vote_display_mode::LocalUserVoteDisplayMode,
|
||||
// person::{Person, PersonInsertForm},
|
||||
// post::{Post, PostInsertForm},
|
||||
// post_report::{PostReport, PostReportForm},
|
||||
// private_message::{PrivateMessage, PrivateMessageInsertForm},
|
||||
// private_message_report::{PrivateMessageReport, PrivateMessageReportForm},
|
||||
// },
|
||||
// traits::{Crud, Joinable, Reportable, Saveable},
|
||||
// utils::{build_db_pool_for_tests, DbPool},
|
||||
// };
|
||||
// use lemmy_utils::error::LemmyResult;
|
||||
// use pretty_assertions::assert_eq;
|
||||
// use serial_test::serial;
|
||||
|
||||
// struct Data {
|
||||
// instance: Instance,
|
||||
// timmy: Person,
|
||||
// sara: Person,
|
||||
// timmy_view: LocalUserView,
|
||||
// community: Community,
|
||||
// timmy_post: Post,
|
||||
// timmy_post_2: Post,
|
||||
// sara_post: Post,
|
||||
// timmy_comment: Comment,
|
||||
// sara_comment: Comment,
|
||||
// sara_comment_2: Comment,
|
||||
// }
|
||||
|
||||
// async fn init_data(pool: &mut DbPool<'_>) -> LemmyResult<Data> {
|
||||
// let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?;
|
||||
|
||||
// let timmy_form = PersonInsertForm::test_form(inserted_instance.id, "timmy_pcv");
|
||||
// let inserted_timmy = Person::create(pool, &timmy_form).await?;
|
||||
// let timmy_local_user_form = LocalUserInsertForm::test_form(inserted_timmy.id);
|
||||
// let timmy_local_user = LocalUser::create(pool, &timmy_local_user_form, vec![]).await?;
|
||||
// let timmy_view = LocalUserView {
|
||||
// local_user: timmy_local_user,
|
||||
// local_user_vote_display_mode: LocalUserVoteDisplayMode::default(),
|
||||
// person: inserted_timmy.clone(),
|
||||
// counts: Default::default(),
|
||||
// };
|
||||
|
||||
// let sara_form = PersonInsertForm::test_form(inserted_instance.id, "sara_pcv");
|
||||
// let inserted_sara = Person::create(pool, &sara_form).await?;
|
||||
|
||||
// let community_form = CommunityInsertForm::new(
|
||||
// inserted_instance.id,
|
||||
// "test community pcv".to_string(),
|
||||
// "nada".to_owned(),
|
||||
// "pubkey".to_string(),
|
||||
// );
|
||||
// let inserted_community = Community::create(pool, &community_form).await?;
|
||||
|
||||
// let timmy_post_form = PostInsertForm::new(
|
||||
// "timmy post prv".into(),
|
||||
// inserted_timmy.id,
|
||||
// inserted_community.id,
|
||||
// );
|
||||
// let timmy_post = Post::create(pool, &timmy_post_form).await?;
|
||||
|
||||
// let timmy_post_form_2 = PostInsertForm::new(
|
||||
// "timmy post prv 2".into(),
|
||||
// inserted_timmy.id,
|
||||
// inserted_community.id,
|
||||
// );
|
||||
// let timmy_post_2 = Post::create(pool, &timmy_post_form_2).await?;
|
||||
|
||||
// let sara_post_form = PostInsertForm::new(
|
||||
// "sara post prv".into(),
|
||||
// inserted_sara.id,
|
||||
// inserted_community.id,
|
||||
// );
|
||||
// let sara_post = Post::create(pool, &sara_post_form).await?;
|
||||
|
||||
// let timmy_comment_form =
|
||||
// CommentInsertForm::new(inserted_timmy.id, timmy_post.id, "timmy comment prv".into());
|
||||
// let timmy_comment = Comment::create(pool, &timmy_comment_form, None).await?;
|
||||
|
||||
// let sara_comment_form =
|
||||
// CommentInsertForm::new(inserted_sara.id, timmy_post.id, "sara comment prv".into());
|
||||
// let sara_comment = Comment::create(pool, &sara_comment_form, None).await?;
|
||||
|
||||
// let sara_comment_form_2 = CommentInsertForm::new(
|
||||
// inserted_sara.id,
|
||||
// timmy_post_2.id,
|
||||
// "sara comment prv 2".into(),
|
||||
// );
|
||||
// let sara_comment_2 = Comment::create(pool, &sara_comment_form_2, None).await?;
|
||||
|
||||
// Ok(Data {
|
||||
// instance: inserted_instance,
|
||||
// timmy: inserted_timmy,
|
||||
// sara: inserted_sara,
|
||||
// timmy_view,
|
||||
// community: inserted_community,
|
||||
// timmy_post,
|
||||
// timmy_post_2,
|
||||
// sara_post,
|
||||
// timmy_comment,
|
||||
// sara_comment,
|
||||
// sara_comment_2,
|
||||
// })
|
||||
// }
|
||||
|
||||
// async fn cleanup(data: Data, pool: &mut DbPool<'_>) -> LemmyResult<()> {
|
||||
// Instance::delete(pool, data.instance.id).await?;
|
||||
|
||||
// Ok(())
|
||||
// }
|
||||
|
||||
// #[tokio::test]
|
||||
// #[serial]
|
||||
// async fn test_combined() -> LemmyResult<()> {
|
||||
// let pool = &build_db_pool_for_tests();
|
||||
// let pool = &mut pool.into();
|
||||
// let data = init_data(pool).await?;
|
||||
|
||||
// // Do a batch read of timmy
|
||||
// let timmy_content = ProfileCombinedQuery::default().list(pool, &None).await?;
|
||||
// assert_eq!(3, timmy_content.len());
|
||||
|
||||
// // Make sure the report types are correct
|
||||
// if let PersonContentCombinedView::Comment(v) = &timmy_content[0] {
|
||||
// assert_eq!(data.timmy_comment.id, v.comment.id);
|
||||
// assert_eq!(data.timmy.id, v.creator.id);
|
||||
// } else {
|
||||
// panic!("wrong type");
|
||||
// }
|
||||
// if let PersonContentCombinedView::Post(v) = &timmy_content[1] {
|
||||
// assert_eq!(data.timmy_post_2.id, v.post.id);
|
||||
// assert_eq!(data.timmy.id, v.post.creator_id);
|
||||
// } else {
|
||||
// panic!("wrong type");
|
||||
// }
|
||||
// if let PersonContentCombinedView::Post(v) = &timmy_content[2] {
|
||||
// assert_eq!(data.timmy_post.id, v.post.id);
|
||||
// assert_eq!(data.timmy.id, v.post.creator_id);
|
||||
// } else {
|
||||
// panic!("wrong type");
|
||||
// }
|
||||
|
||||
// // Do a batch read of sara
|
||||
// let sara_content = ProfileCombinedQuery::default().list(pool, &None).await?;
|
||||
// assert_eq!(3, sara_content.len());
|
||||
|
||||
// // Make sure the report types are correct
|
||||
// if let PersonContentCombinedView::Comment(v) = &sara_content[0] {
|
||||
// assert_eq!(data.sara_comment_2.id, v.comment.id);
|
||||
// assert_eq!(data.sara.id, v.creator.id);
|
||||
// // This one was to timmy_post_2
|
||||
// assert_eq!(data.timmy_post_2.id, v.post.id);
|
||||
// assert_eq!(data.timmy.id, v.post.creator_id);
|
||||
// } else {
|
||||
// panic!("wrong type");
|
||||
// }
|
||||
// if let PersonContentCombinedView::Comment(v) = &sara_content[1] {
|
||||
// assert_eq!(data.sara_comment.id, v.comment.id);
|
||||
// assert_eq!(data.sara.id, v.creator.id);
|
||||
// assert_eq!(data.timmy_post.id, v.post.id);
|
||||
// assert_eq!(data.timmy.id, v.post.creator_id);
|
||||
// } else {
|
||||
// panic!("wrong type");
|
||||
// }
|
||||
// if let PersonContentCombinedView::Post(v) = &sara_content[2] {
|
||||
// assert_eq!(data.timmy_post.id, v.post.id);
|
||||
// assert_eq!(data.timmy.id, v.post.creator_id);
|
||||
// } else {
|
||||
// panic!("wrong type");
|
||||
// }
|
||||
|
||||
// // Timmy saves sara's comment, and his 2nd post
|
||||
// let save_comment_0_form = CommentSavedForm {
|
||||
// person_id: data.timmy.id,
|
||||
// comment_id: data.sara_comment.id,
|
||||
// };
|
||||
// CommentSaved::save(pool, &save_comment_0_form).await?;
|
||||
|
||||
// // Timmy saves sara's comment, and his 2nd post
|
||||
// let save_comment_0_form = CommentSavedForm {
|
||||
// person_id: data.timmy.id,
|
||||
// comment_id: data.sara_comment.id,
|
||||
// };
|
||||
// CommentSaved::save(pool, &save_comment_0_form).await?;
|
||||
|
||||
// // Do a saved_only query
|
||||
// let timmy_content_saved_only = ProfileCombinedQuery {}.list(pool, &None).await?;
|
||||
|
||||
// cleanup(data, pool).await?;
|
||||
|
||||
// Ok(())
|
||||
// }
|
||||
// }
|
||||
// #[tokio::test]
|
||||
// #[serial]
|
||||
// async fn test_saved_order() -> LemmyResult<()> {
|
||||
// let pool = &build_db_pool_for_tests();
|
||||
// let pool = &mut pool.into();
|
||||
// let data = init_data(pool).await?;
|
||||
|
||||
// // Save two comments
|
||||
// let save_comment_0_form = CommentSavedForm {
|
||||
// person_id: data.timmy_local_user_view.person.id,
|
||||
// comment_id: data.inserted_comment_0.id,
|
||||
// };
|
||||
// CommentSaved::save(pool, &save_comment_0_form).await?;
|
||||
|
||||
// let save_comment_2_form = CommentSavedForm {
|
||||
// person_id: data.timmy_local_user_view.person.id,
|
||||
// comment_id: data.inserted_comment_2.id,
|
||||
// };
|
||||
// CommentSaved::save(pool, &save_comment_2_form).await?;
|
||||
|
||||
// // Fetch the saved comments
|
||||
// let comments = CommentQuery {
|
||||
// local_user: Some(&data.timmy_local_user_view.local_user),
|
||||
// saved_only: Some(true),
|
||||
// ..Default::default()
|
||||
// }
|
||||
// .list(&data.site, pool)
|
||||
// .await?;
|
||||
|
||||
// // There should only be two comments
|
||||
// assert_eq!(2, comments.len());
|
||||
|
||||
// // The first comment, should be the last one saved (descending order)
|
||||
// assert_eq!(comments[0].comment.id, data.inserted_comment_2.id);
|
||||
|
||||
// // The second comment, should be the first one saved
|
||||
// assert_eq!(comments[1].comment.id, data.inserted_comment_0.id);
|
||||
|
||||
// cleanup(data, pool).await
|
||||
// }
|
||||
// #[tokio::test]
|
||||
// #[serial]
|
||||
// async fn post_listing_saved_only() -> LemmyResult<()> {
|
||||
// let pool = &build_db_pool()?;
|
||||
// let pool = &mut pool.into();
|
||||
// let data = init_data(pool).await?;
|
||||
|
||||
// // Save only the bot post
|
||||
// // The saved_only should only show the bot post
|
||||
// let post_save_form =
|
||||
// PostSavedForm::new(data.inserted_bot_post.id, data.local_user_view.person.id);
|
||||
// PostSaved::save(pool, &post_save_form).await?;
|
||||
|
||||
// // Read the saved only
|
||||
// let read_saved_post_listing = PostQuery {
|
||||
// community_id: Some(data.inserted_community.id),
|
||||
// saved_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_saved_post_listing));
|
||||
|
||||
// cleanup(data, pool).await
|
||||
// }
|
|
@ -287,15 +287,7 @@ fn queries<'a>() -> Queries<
|
|||
query = query.filter(post_aggregates::comments.eq(0));
|
||||
};
|
||||
|
||||
// If its saved only, then filter, and order by the saved time, not the comment creation time.
|
||||
if options.saved_only.unwrap_or_default() {
|
||||
query = query
|
||||
.filter(post_actions::saved.is_not_null())
|
||||
.then_order_by(post_actions::saved.desc());
|
||||
}
|
||||
// Only hide the read posts, if the saved_only is false. Otherwise ppl with the hide_read
|
||||
// setting wont be able to see saved posts.
|
||||
else if !options
|
||||
if !options
|
||||
.show_read
|
||||
.unwrap_or(options.local_user.show_read_posts())
|
||||
{
|
||||
|
@ -488,7 +480,6 @@ pub struct PostQuery<'a> {
|
|||
pub local_user: Option<&'a LocalUser>,
|
||||
pub search_term: Option<String>,
|
||||
pub url_only: Option<bool>,
|
||||
pub saved_only: Option<bool>,
|
||||
pub liked_only: Option<bool>,
|
||||
pub disliked_only: Option<bool>,
|
||||
pub title_only: Option<bool>,
|
||||
|
@ -646,13 +637,11 @@ mod tests {
|
|||
PostLikeForm,
|
||||
PostRead,
|
||||
PostReadForm,
|
||||
PostSaved,
|
||||
PostSavedForm,
|
||||
PostUpdateForm,
|
||||
},
|
||||
site::Site,
|
||||
},
|
||||
traits::{Bannable, Blockable, Crud, Followable, Joinable, Likeable, Saveable},
|
||||
traits::{Bannable, Blockable, Crud, Followable, Joinable, Likeable},
|
||||
utils::{build_db_pool, build_db_pool_for_tests, get_conn, uplete, DbPool, RANK_DEFAULT},
|
||||
CommunityVisibility,
|
||||
PostSortType,
|
||||
|
@ -1090,34 +1079,6 @@ mod tests {
|
|||
cleanup(data, pool).await
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn post_listing_saved_only() -> LemmyResult<()> {
|
||||
let pool = &build_db_pool()?;
|
||||
let pool = &mut pool.into();
|
||||
let data = init_data(pool).await?;
|
||||
|
||||
// Save only the bot post
|
||||
// The saved_only should only show the bot post
|
||||
let post_save_form =
|
||||
PostSavedForm::new(data.inserted_bot_post.id, data.local_user_view.person.id);
|
||||
PostSaved::save(pool, &post_save_form).await?;
|
||||
|
||||
// Read the saved only
|
||||
let read_saved_post_listing = PostQuery {
|
||||
community_id: Some(data.inserted_community.id),
|
||||
saved_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_saved_post_listing));
|
||||
|
||||
cleanup(data, pool).await
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn creator_info() -> LemmyResult<()> {
|
||||
|
|
|
@ -1,278 +0,0 @@
|
|||
use crate::structs::{
|
||||
CommentView,
|
||||
LocalUserView,
|
||||
PostView,
|
||||
ProfileCombinedPaginationCursor,
|
||||
ProfileCombinedView,
|
||||
ProfileCombinedViewInternal,
|
||||
};
|
||||
use diesel::{
|
||||
result::Error,
|
||||
BoolExpressionMethods,
|
||||
ExpressionMethods,
|
||||
JoinOnDsl,
|
||||
NullableExpressionMethods,
|
||||
QueryDsl,
|
||||
SelectableHelper,
|
||||
};
|
||||
use diesel_async::RunQueryDsl;
|
||||
use i_love_jesus::PaginatedQueryBuilder;
|
||||
use lemmy_db_schema::{
|
||||
aliases::creator_community_actions,
|
||||
newtypes::{CommunityId, PersonId},
|
||||
schema::{
|
||||
comment,
|
||||
comment_actions,
|
||||
comment_aggregates,
|
||||
community,
|
||||
community_actions,
|
||||
image_details,
|
||||
local_user,
|
||||
person,
|
||||
person_actions,
|
||||
post,
|
||||
post_actions,
|
||||
post_aggregates,
|
||||
profile_combined,
|
||||
},
|
||||
source::{
|
||||
combined::profile::{profile_combined_keys as key, ProfileCombined},
|
||||
community::CommunityFollower,
|
||||
},
|
||||
utils::{actions, actions_alias, functions::coalesce, get_conn, DbPool, ReverseTimestampKey},
|
||||
};
|
||||
use lemmy_utils::error::LemmyResult;
|
||||
|
||||
impl ProfileCombinedPaginationCursor {
|
||||
// get cursor for page that starts immediately after the given post
|
||||
pub fn after_post(view: &ProfileCombinedView) -> ProfileCombinedPaginationCursor {
|
||||
let (prefix, id) = match view {
|
||||
ProfileCombinedView::Comment(v) => ('C', v.comment.id.0),
|
||||
ProfileCombinedView::Post(v) => ('P', v.post.id.0),
|
||||
};
|
||||
// hex encoding to prevent ossification
|
||||
ProfileCombinedPaginationCursor(format!("{prefix}{id:x}"))
|
||||
}
|
||||
|
||||
pub async fn read(&self, pool: &mut DbPool<'_>) -> Result<PaginationCursorData, Error> {
|
||||
let err_msg = || Error::QueryBuilderError("Could not parse pagination token".into());
|
||||
let mut query = profile_combined::table
|
||||
.select(ProfileCombined::as_select())
|
||||
.into_boxed();
|
||||
let (prefix, id_str) = self.0.split_at_checked(1).ok_or_else(err_msg)?;
|
||||
let id = i32::from_str_radix(id_str, 16).map_err(|_err| err_msg())?;
|
||||
query = match prefix {
|
||||
"C" => query.filter(profile_combined::comment_id.eq(id)),
|
||||
"P" => query.filter(profile_combined::post_id.eq(id)),
|
||||
_ => return Err(err_msg()),
|
||||
};
|
||||
let token = query.first(&mut get_conn(pool).await?).await?;
|
||||
|
||||
Ok(PaginationCursorData(token))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct PaginationCursorData(ProfileCombined);
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ProfileCombinedQuery {
|
||||
pub creator_id: PersonId,
|
||||
pub community_id: Option<CommunityId>,
|
||||
pub saved_only: Option<bool>,
|
||||
pub page_after: Option<PaginationCursorData>,
|
||||
pub page_back: Option<bool>,
|
||||
}
|
||||
|
||||
impl ProfileCombinedQuery {
|
||||
pub async fn list(
|
||||
self,
|
||||
pool: &mut DbPool<'_>,
|
||||
user: &Option<LocalUserView>,
|
||||
) -> LemmyResult<Vec<ProfileCombinedView>> {
|
||||
let my_person_id = user
|
||||
.as_ref()
|
||||
.map(|u| u.local_user.person_id)
|
||||
.unwrap_or(PersonId(-1));
|
||||
let item_creator = person::id;
|
||||
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
|
||||
// Notes: since the post_id and comment_id are optional columns,
|
||||
// many joins must use an OR condition.
|
||||
// For example, the creator must be the person table joined to either:
|
||||
// - post.creator_id
|
||||
// - comment.creator_id
|
||||
let mut query = profile_combined::table
|
||||
// The comment
|
||||
.left_join(comment::table.on(profile_combined::comment_id.eq(comment::id.nullable())))
|
||||
// The post
|
||||
.inner_join(
|
||||
post::table.on(
|
||||
profile_combined::post_id
|
||||
.eq(post::id.nullable())
|
||||
.or(comment::post_id.nullable().eq(profile_combined::post_id)),
|
||||
),
|
||||
)
|
||||
// The item creator
|
||||
.inner_join(
|
||||
person::table.on(
|
||||
comment::creator_id
|
||||
.eq(person::id)
|
||||
.or(post::creator_id.eq(person::id)),
|
||||
),
|
||||
)
|
||||
// The community
|
||||
.inner_join(community::table.on(post::community_id.eq(community::id)))
|
||||
.left_join(actions_alias(
|
||||
creator_community_actions,
|
||||
item_creator,
|
||||
post::community_id,
|
||||
))
|
||||
.left_join(
|
||||
local_user::table.on(
|
||||
item_creator
|
||||
.eq(local_user::person_id)
|
||||
.and(local_user::admin.eq(true)),
|
||||
),
|
||||
)
|
||||
.left_join(actions(
|
||||
community_actions::table,
|
||||
Some(my_person_id),
|
||||
post::community_id,
|
||||
))
|
||||
.left_join(actions(post_actions::table, Some(my_person_id), post::id))
|
||||
.left_join(actions(
|
||||
person_actions::table,
|
||||
Some(my_person_id),
|
||||
item_creator,
|
||||
))
|
||||
.inner_join(post_aggregates::table.on(post::id.eq(post_aggregates::post_id)))
|
||||
.left_join(
|
||||
comment_aggregates::table
|
||||
.on(profile_combined::comment_id.eq(comment_aggregates::comment_id.nullable())),
|
||||
)
|
||||
.left_join(actions(
|
||||
comment_actions::table,
|
||||
Some(my_person_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((
|
||||
// Post-specific
|
||||
post_aggregates::all_columns,
|
||||
coalesce(
|
||||
post_aggregates::comments.nullable() - post_actions::read_comments_amount.nullable(),
|
||||
post_aggregates::comments,
|
||||
),
|
||||
post_actions::saved.nullable().is_not_null(),
|
||||
post_actions::read.nullable().is_not_null(),
|
||||
post_actions::hidden.nullable().is_not_null(),
|
||||
post_actions::like_score.nullable(),
|
||||
image_details::all_columns.nullable(),
|
||||
// Comment-specific
|
||||
comment::all_columns.nullable(),
|
||||
comment_aggregates::all_columns.nullable(),
|
||||
comment_actions::saved.nullable().is_not_null(),
|
||||
comment_actions::like_score.nullable(),
|
||||
// Shared
|
||||
post::all_columns,
|
||||
community::all_columns,
|
||||
person::all_columns,
|
||||
CommunityFollower::select_subscribed_type(),
|
||||
local_user::admin.nullable().is_not_null(),
|
||||
creator_community_actions
|
||||
.field(community_actions::became_moderator)
|
||||
.nullable()
|
||||
.is_not_null(),
|
||||
creator_community_actions
|
||||
.field(community_actions::received_ban)
|
||||
.nullable()
|
||||
.is_not_null(),
|
||||
person_actions::blocked.nullable().is_not_null(),
|
||||
community_actions::received_ban.nullable().is_not_null(),
|
||||
))
|
||||
.into_boxed();
|
||||
|
||||
if let Some(community_id) = self.community_id {
|
||||
query = query.filter(community::id.eq(community_id));
|
||||
}
|
||||
|
||||
// If its saved only, then filter
|
||||
if self.saved_only.unwrap_or_default() {
|
||||
query = query.filter(
|
||||
comment_actions::saved
|
||||
.is_not_null()
|
||||
.or(post_actions::saved.is_not_null()),
|
||||
)
|
||||
}
|
||||
|
||||
let mut query = PaginatedQueryBuilder::new(query);
|
||||
|
||||
let page_after = self.page_after.map(|c| c.0);
|
||||
|
||||
if self.page_back.unwrap_or_default() {
|
||||
query = query.before(page_after).limit_and_offset_from_end();
|
||||
} else {
|
||||
query = query.after(page_after);
|
||||
}
|
||||
|
||||
// Sorting by published
|
||||
query = query
|
||||
.then_desc(ReverseTimestampKey(key::published))
|
||||
// Tie breaker
|
||||
.then_desc(key::id);
|
||||
|
||||
let res = query.load::<ProfileCombinedViewInternal>(conn).await?;
|
||||
|
||||
// Map the query results to the enum
|
||||
let out = res.into_iter().filter_map(map_to_enum).collect();
|
||||
|
||||
Ok(out)
|
||||
}
|
||||
}
|
||||
|
||||
/// Maps the combined DB row to an enum
|
||||
fn map_to_enum(view: ProfileCombinedViewInternal) -> Option<ProfileCombinedView> {
|
||||
// Use for a short alias
|
||||
let v = view;
|
||||
|
||||
if let (Some(comment), Some(counts)) = (v.comment, v.comment_counts) {
|
||||
Some(ProfileCombinedView::Comment(CommentView {
|
||||
comment,
|
||||
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_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.post_saved,
|
||||
read: v.post_read,
|
||||
hidden: v.post_hidden,
|
||||
my_vote: v.my_post_vote,
|
||||
image_details: v.image_details,
|
||||
banned_from_community: v.banned_from_community,
|
||||
}))
|
||||
}
|
||||
}
|
|
@ -1,11 +1,14 @@
|
|||
use crate::structs::{
|
||||
CommentReportView,
|
||||
LocalUserView,
|
||||
PostReportView,
|
||||
PrivateMessageReportView,
|
||||
ReportCombinedPaginationCursor,
|
||||
ReportCombinedView,
|
||||
ReportCombinedViewInternal,
|
||||
use crate::{
|
||||
structs::{
|
||||
CommentReportView,
|
||||
LocalUserView,
|
||||
PostReportView,
|
||||
PrivateMessageReportView,
|
||||
ReportCombinedPaginationCursor,
|
||||
ReportCombinedView,
|
||||
ReportCombinedViewInternal,
|
||||
},
|
||||
InternalToCombinedView,
|
||||
};
|
||||
use diesel::{
|
||||
result::Error,
|
||||
|
@ -149,9 +152,10 @@ impl ReportCombinedQuery {
|
|||
user: &LocalUserView,
|
||||
) -> LemmyResult<Vec<ReportCombinedView>> {
|
||||
let my_person_id = user.local_user.person_id;
|
||||
let report_creator = person::id;
|
||||
let item_creator = aliases::person1.field(person::id);
|
||||
|
||||
let resolver = aliases::person2.field(person::id).nullable();
|
||||
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
|
||||
// Notes: since the post_report_id and comment_report_id are optional columns,
|
||||
|
@ -167,9 +171,9 @@ impl ReportCombinedQuery {
|
|||
.inner_join(
|
||||
person::table.on(
|
||||
post_report::creator_id
|
||||
.eq(person::id)
|
||||
.or(comment_report::creator_id.eq(person::id))
|
||||
.or(private_message_report::creator_id.eq(person::id)),
|
||||
.eq(report_creator)
|
||||
.or(comment_report::creator_id.eq(report_creator))
|
||||
.or(private_message_report::creator_id.eq(report_creator)),
|
||||
),
|
||||
)
|
||||
// The comment
|
||||
|
@ -188,7 +192,8 @@ impl ReportCombinedQuery {
|
|||
),
|
||||
)
|
||||
// The item creator
|
||||
// You can now use aliases::person1.field(person::id) / item_creator for all the item actions
|
||||
// You can now use aliases::person1.field(person::id) / item_creator
|
||||
// for all the item actions
|
||||
.inner_join(
|
||||
aliases::person1.on(
|
||||
post::creator_id
|
||||
|
@ -324,81 +329,84 @@ impl ReportCombinedQuery {
|
|||
let res = query.load::<ReportCombinedViewInternal>(conn).await?;
|
||||
|
||||
// Map the query results to the enum
|
||||
let out = res.into_iter().filter_map(map_to_enum).collect();
|
||||
let out = res.into_iter().filter_map(|u| u.map_to_enum()).collect();
|
||||
|
||||
Ok(out)
|
||||
}
|
||||
}
|
||||
|
||||
/// Maps the combined DB row to an enum
|
||||
fn map_to_enum(view: ReportCombinedViewInternal) -> Option<ReportCombinedView> {
|
||||
// Use for a short alias
|
||||
let v = view;
|
||||
impl InternalToCombinedView for ReportCombinedViewInternal {
|
||||
type CombinedView = ReportCombinedView;
|
||||
|
||||
if let (Some(post_report), Some(post), Some(community), Some(unread_comments), Some(counts)) = (
|
||||
v.post_report,
|
||||
v.post.clone(),
|
||||
v.community.clone(),
|
||||
v.post_unread_comments,
|
||||
v.post_counts,
|
||||
) {
|
||||
Some(ReportCombinedView::Post(PostReportView {
|
||||
post_report,
|
||||
post,
|
||||
community,
|
||||
unread_comments,
|
||||
counts,
|
||||
creator: v.report_creator,
|
||||
post_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.post_saved,
|
||||
read: v.post_read,
|
||||
hidden: v.post_hidden,
|
||||
my_vote: v.my_post_vote,
|
||||
resolver: v.resolver,
|
||||
}))
|
||||
} else if let (Some(comment_report), Some(comment), Some(counts), Some(post), Some(community)) = (
|
||||
v.comment_report,
|
||||
v.comment,
|
||||
v.comment_counts,
|
||||
v.post.clone(),
|
||||
v.community.clone(),
|
||||
) {
|
||||
Some(ReportCombinedView::Comment(CommentReportView {
|
||||
comment_report,
|
||||
comment,
|
||||
counts,
|
||||
post,
|
||||
community,
|
||||
creator: v.report_creator,
|
||||
comment_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,
|
||||
resolver: v.resolver,
|
||||
}))
|
||||
} else if let (Some(private_message_report), Some(private_message)) =
|
||||
(v.private_message_report, v.private_message)
|
||||
{
|
||||
Some(ReportCombinedView::PrivateMessage(
|
||||
PrivateMessageReportView {
|
||||
private_message_report,
|
||||
private_message,
|
||||
fn map_to_enum(&self) -> Option<Self::CombinedView> {
|
||||
// Use for a short alias
|
||||
let v = self.clone();
|
||||
|
||||
if let (Some(post_report), Some(post), Some(community), Some(unread_comments), Some(counts)) = (
|
||||
v.post_report,
|
||||
v.post.clone(),
|
||||
v.community.clone(),
|
||||
v.post_unread_comments,
|
||||
v.post_counts,
|
||||
) {
|
||||
Some(ReportCombinedView::Post(PostReportView {
|
||||
post_report,
|
||||
post,
|
||||
community,
|
||||
unread_comments,
|
||||
counts,
|
||||
creator: v.report_creator,
|
||||
private_message_creator: v.item_creator,
|
||||
post_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.post_saved,
|
||||
read: v.post_read,
|
||||
hidden: v.post_hidden,
|
||||
my_vote: v.my_post_vote,
|
||||
resolver: v.resolver,
|
||||
},
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}))
|
||||
} else if let (Some(comment_report), Some(comment), Some(counts), Some(post), Some(community)) = (
|
||||
v.comment_report,
|
||||
v.comment,
|
||||
v.comment_counts,
|
||||
v.post,
|
||||
v.community,
|
||||
) {
|
||||
Some(ReportCombinedView::Comment(CommentReportView {
|
||||
comment_report,
|
||||
comment,
|
||||
counts,
|
||||
post,
|
||||
community,
|
||||
creator: v.report_creator,
|
||||
comment_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,
|
||||
resolver: v.resolver,
|
||||
}))
|
||||
} else if let (Some(private_message_report), Some(private_message)) =
|
||||
(v.private_message_report, v.private_message)
|
||||
{
|
||||
Some(ReportCombinedView::PrivateMessage(
|
||||
PrivateMessageReportView {
|
||||
private_message_report,
|
||||
private_message,
|
||||
creator: v.report_creator,
|
||||
private_message_creator: v.item_creator,
|
||||
resolver: v.resolver,
|
||||
},
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -132,11 +132,17 @@ pub struct PaginationCursor(pub String);
|
|||
#[cfg_attr(feature = "full", ts(export))]
|
||||
pub struct ReportCombinedPaginationCursor(pub String);
|
||||
|
||||
/// like PaginationCursor but for the profile_combined table
|
||||
/// like PaginationCursor but for the person_content_combined table
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "full", derive(ts_rs::TS))]
|
||||
#[cfg_attr(feature = "full", ts(export))]
|
||||
pub struct ProfileCombinedPaginationCursor(pub String);
|
||||
pub struct PersonContentCombinedPaginationCursor(pub String);
|
||||
|
||||
/// like PaginationCursor but for the person_saved_combined table
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "full", derive(ts_rs::TS))]
|
||||
#[cfg_attr(feature = "full", ts(export))]
|
||||
pub struct PersonSavedCombinedPaginationCursor(pub String);
|
||||
|
||||
#[skip_serializing_none]
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
|
||||
|
@ -299,8 +305,8 @@ pub enum ReportCombinedView {
|
|||
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
|
||||
#[cfg_attr(feature = "full", derive(Queryable))]
|
||||
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
|
||||
/// A combined profile view
|
||||
pub struct ProfileCombinedViewInternal {
|
||||
/// A combined person_content view
|
||||
pub struct PersonContentViewInternal {
|
||||
// Post-specific
|
||||
pub post_counts: PostAggregates,
|
||||
pub post_unread_comments: i64,
|
||||
|
@ -331,7 +337,7 @@ pub struct ProfileCombinedViewInternal {
|
|||
#[cfg_attr(feature = "full", ts(export))]
|
||||
// Use serde's internal tagging, to work easier with javascript libraries
|
||||
#[serde(tag = "type_")]
|
||||
pub enum ProfileCombinedView {
|
||||
pub enum PersonContentCombinedView {
|
||||
Post(PostView),
|
||||
Comment(CommentView),
|
||||
}
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
DROP TABLE person_content_combined;
|
||||
DROP TABLE person_saved_combined;
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
-- Creates combined tables for
|
||||
-- person_content: (comment, post)
|
||||
-- person_saved: (comment, post)
|
||||
CREATE TABLE person_content_combined (
|
||||
id serial PRIMARY KEY,
|
||||
published timestamptz NOT NULL,
|
||||
post_id int UNIQUE REFERENCES post 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
|
||||
CHECK ((post_id IS NOT NULL)::integer + (comment_id IS NOT NULL)::integer = 1)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_person_content_combined_published ON person_content_combined (published DESC, id DESC);
|
||||
|
||||
CREATE INDEX idx_person_content_combined_published_asc ON person_content_combined (reverse_timestamp_sort (published) DESC, id DESC);
|
||||
|
||||
-- Updating the history
|
||||
INSERT INTO person_content_combined (published, post_id)
|
||||
SELECT
|
||||
published,
|
||||
id
|
||||
FROM
|
||||
post;
|
||||
|
||||
INSERT INTO person_content_combined (published, comment_id)
|
||||
SELECT
|
||||
published,
|
||||
id
|
||||
FROM
|
||||
comment;
|
||||
|
||||
-- This one is special, because you use the saved date, not the ordinary published
|
||||
CREATE TABLE person_saved_combined (
|
||||
id serial PRIMARY KEY,
|
||||
published timestamptz NOT NULL,
|
||||
post_id int UNIQUE REFERENCES post 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
|
||||
CHECK ((post_id IS NOT NULL)::integer + (comment_id IS NOT NULL)::integer = 1)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_person_saved_combined_published ON person_saved_combined (published DESC, id DESC);
|
||||
|
||||
CREATE INDEX idx_person_saved_combined_published_asc ON person_saved_combined (reverse_timestamp_sort (published) DESC, id DESC);
|
||||
|
||||
-- Updating the history
|
||||
INSERT INTO person_saved_combined (published, post_id)
|
||||
SELECT
|
||||
saved,
|
||||
post_id
|
||||
FROM
|
||||
post_actions
|
||||
WHERE
|
||||
saved IS NOT NULL;
|
||||
|
||||
INSERT INTO person_saved_combined (published, comment_id)
|
||||
SELECT
|
||||
saved,
|
||||
comment_id
|
||||
FROM
|
||||
comment_actions
|
||||
WHERE
|
||||
saved IS NOT NULL;
|
||||
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
DROP TABLE profile_combined;
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
-- Creates combined tables for
|
||||
-- Profile: (comment, post)
|
||||
CREATE TABLE profile_combined (
|
||||
id serial PRIMARY KEY,
|
||||
published timestamptz NOT NULL,
|
||||
post_id int UNIQUE REFERENCES post 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
|
||||
CHECK ((post_id IS NOT NULL)::integer + (comment_id IS NOT NULL)::integer = 1)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_profile_combined_published ON profile_combined (published DESC, id DESC);
|
||||
|
||||
CREATE INDEX idx_profile_combined_published_asc ON profile_combined (reverse_timestamp_sort (published) DESC, id DESC);
|
||||
|
||||
-- Updating the history
|
||||
INSERT INTO profile_combined (published, post_id)
|
||||
SELECT
|
||||
published,
|
||||
id
|
||||
FROM
|
||||
post;
|
||||
|
||||
INSERT INTO profile_combined (published, comment_id)
|
||||
SELECT
|
||||
published,
|
||||
id
|
||||
FROM
|
||||
comment;
|
||||
|
|
@ -142,6 +142,7 @@ use lemmy_api_crud::{
|
|||
};
|
||||
use lemmy_apub::api::{
|
||||
list_comments::list_comments,
|
||||
list_person_content::list_person_content,
|
||||
list_posts::list_posts,
|
||||
read_community::get_community,
|
||||
read_person::read_person,
|
||||
|
@ -338,6 +339,9 @@ pub fn config(cfg: &mut ServiceConfig, rate_limit: &RateLimitCell) {
|
|||
scope("/user")
|
||||
.wrap(rate_limit.message())
|
||||
.route("", get().to(read_person))
|
||||
.route("/content", get().to(list_person_content))
|
||||
// TODO move this to /account/saved after http routes
|
||||
// .route("/saved", get().to(read_person_saved))
|
||||
.route("/mention", get().to(list_mentions))
|
||||
.route(
|
||||
"/mention/mark_as_read",
|
||||
|
|
Loading…
Reference in a new issue