Move read only posts to its own endpoint. (#5610)

* migration

* update code

* tests

* triggers

* fix

* fmt

* clippy

* post aggregate migration

* changes for post aggregate code

* wip: update tests for post aggregate

* format

* fix partialeq

* trigger fix

* fix post insert trigger

* wip

* reorder

* fixes

* community aggregate migration

* update code

* triggers

* person aggregate migration

* person aggregate code

* person triggers

* test fixes

* fix scheduled task

* update api tests

* site_aggregates to local_site migration

* site_aggregates code changes

* triggers, tests

* more fixes

* Rename PersonPostAggregates to PostActions

* Merge local_user_vote_display_mode into local_user

* fix schema

* Extracting pagination cursor utils into a trait.

- Fixes #5275

* Refactoring to avoid stack overflows.

* Fixing api_common feature.

* Adding pagination for GetBannedPersons.

- Must come after #5424
- Fixes #2847

* Starting to work on removing rest of page / limit.

* Issues with community pagination.

* Rename the traits and paginationcursor::new

* remove duplicate fields

* remove "aggregates" from index names

* uncomment indices

* if count = 0

* Using combined trait.

* remove commentaggregates

* Fix triggers in remove aggregates tables pr (#5451)

* prevent all db_schema test errors

* fix the delete_comments_before_post problem in a way that doesn't affect the returned number of affected rows

* remove unnecessary recursion checks and add comment to remaining check

* clean up

* Fixing SQL format.

* Update triggers.sql

* Update triggers.sql

* Update triggers.sql

* Update triggers.sql

* remove update of deleted column

---------

Co-authored-by: Dessalines <tyhou13@gmx.com>

* rename migration

* Fix migration errors

* Move community.hidden to visibility (fixes #5458)

* Removing empty files.

* Fixing person_saved_combined. (#5481)

* Remove comment and post specific action structs. #5473

* Doing reports

* fix up migration by dropping index

* also add enum variant `LocalOnlyPublic`, rename `LocalOnly` to `LocalOnlyPrivate`

fixes #5351

* fix column order in down.sql

* wip

* Moving blocks.

* Adding a few more views.

* more wip

* fixes

* migration for modlog

* fix migration

* Working views and schema.

* Fix ts_optionals.

* wip

* db_schema compiling

* make the code compile

* Merging from main.

* lint

* Fixing SQL format.

* fix down migration

* Fixing api tests.

* Adding field comments for the actions tables.

* Refactoring CommunityFollower to include follow_state

* fix test

* make hidden status federate

* ts attr

* fix

* fix api test

* Update crates/api/src/reports/post_report/resolve.rs

Co-authored-by: Nutomic <me@nutomic.com>

* Addressing PR comments

* Fix ts export.

* update api client

* review

* Extracting filter_not_hidden_or_is_subscribed (#5497)

* Extracting filter_not_hidden_or_is_subscribed

* Cleanup.

* Cleanup 2.

* Remove follower_state_helper function.

* Cleaning up some utils functions.

* rename hidden to unlisted

* Updating lemmy-js-client.

* Fixing a few cases.

* Fixing list_banned.

* Starting to convert to lemmy results.

* Fixing vote_view.

* Close to finishing up errors.

* Got compiling.

* Remove tracing for CI.

* Removing unused errors.

* Fixing merge from main.

* lower_name community_view sort.

* Syntax errors.

* Removing rest of Result from db_schema, db_views.

* Finally compiling again.

* fmt.

* Subpath tries again.

* Adding some more indexes./

* Fixing shear.

* Fix keyword_blocks test.

* @dullbananas fixes to Subpath, fixing comment tests.

* sql fmt

* Fixing unused error.

* API test fixing

* Moving read_only_posts to its own endpoint, sorted by read date.

- Fixes #5505

* Adding a first_id helper function.

* Fixing api_common

* Addressing PR comments

* Addressing PR comments 1.

* Using id desc.

* Addressing PR comments 2.

* Removing the reverse_timestamp keys for the post table.

* Make community_title and community_lower_name indexes desc

* Remove featured_community from post sort

* Forgot to drop index.

* Addressing PR comments

---------

Co-authored-by: Felix Ableitner <me@nutomic.com>
Co-authored-by: dullbananas <dull.bananas0@gmail.com>
This commit is contained in:
Dessalines 2025-04-23 13:59:10 -04:00 committed by GitHub
parent fe50d8ad67
commit 88dbf58373
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 149 additions and 27 deletions

View file

@ -0,0 +1,40 @@
use activitypub_federation::config::Data;
use actix_web::web::{Json, Query};
use lemmy_api_common::{
context::LemmyContext,
person::{ListPersonRead, ListPersonReadResponse},
};
use lemmy_db_schema::source::post::PostActions;
use lemmy_db_views_local_user::LocalUserView;
use lemmy_db_views_post::PostView;
use lemmy_utils::error::LemmyResult;
pub async fn list_person_read(
data: Query<ListPersonRead>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> LemmyResult<Json<ListPersonReadResponse>> {
let cursor_data = if let Some(cursor) = &data.page_cursor {
Some(PostActions::from_cursor(cursor, &mut context.pool()).await?)
} else {
None
};
let read = PostView::list_read(
&mut context.pool(),
&local_user_view.person,
cursor_data,
data.page_back,
data.limit,
)
.await?;
let next_page = read.last().map(PostView::to_post_actions_cursor);
let prev_page = read.first().map(PostView::to_post_actions_cursor);
Ok(Json(ListPersonReadResponse {
read,
next_page,
prev_page,
}))
}

View file

@ -9,6 +9,7 @@ pub mod get_captcha;
pub mod list_banned;
pub mod list_logins;
pub mod list_media;
pub mod list_read;
pub mod list_saved;
pub mod login;
pub mod logout;

View file

@ -20,6 +20,7 @@ use lemmy_db_views_local_image::LocalImageView;
use lemmy_db_views_person::PersonView;
use lemmy_db_views_person_content_combined::PersonContentCombinedView;
use lemmy_db_views_person_saved_combined::PersonSavedCombinedView;
use lemmy_db_views_post::PostView;
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
#[cfg(feature = "full")]
@ -312,6 +313,34 @@ pub struct ListPersonSavedResponse {
pub prev_page: Option<PaginationCursor>,
}
#[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 read posts.
pub struct ListPersonRead {
#[cfg_attr(feature = "full", ts(optional))]
pub page_cursor: Option<PaginationCursor>,
#[cfg_attr(feature = "full", ts(optional))]
pub page_back: Option<bool>,
#[cfg_attr(feature = "full", ts(optional))]
pub limit: Option<i64>,
}
#[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
/// You read posts response.
pub struct ListPersonReadResponse {
pub read: Vec<PostView>,
/// the pagination cursor to use to fetch the next page
#[cfg_attr(feature = "full", ts(optional))]
pub next_page: Option<PaginationCursor>,
#[cfg_attr(feature = "full", ts(optional))]
pub prev_page: Option<PaginationCursor>,
}
#[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]

View file

@ -1,5 +1,5 @@
use crate::{
newtypes::{CommunityId, DbUrl, InstanceId, PersonId, PostId},
newtypes::{CommunityId, DbUrl, InstanceId, PaginationCursor, PersonId, PostId},
source::post::{
Post,
PostActions,
@ -468,6 +468,18 @@ impl PostActions {
.await
.with_lemmy_type(LemmyErrorType::NotFound)
}
pub async fn from_cursor(cursor: &PaginationCursor, pool: &mut DbPool<'_>) -> LemmyResult<Self> {
let pids = cursor.prefixes_and_ids();
let (_, person_id) = pids
.as_slice()
.first()
.ok_or(LemmyErrorType::CouldntParsePaginationToken)?;
let (_, post_id) = pids
.get(1)
.ok_or(LemmyErrorType::CouldntParsePaginationToken)?;
Self::read(pool, PostId(*post_id), PersonId(*person_id)).await
}
}
#[cfg(test)]

View file

@ -16,13 +16,14 @@ use diesel::{
TextExpressionMethods,
};
use diesel_async::RunQueryDsl;
use i_love_jesus::asc_if;
use i_love_jesus::{asc_if, SortDirection};
use lemmy_db_schema::{
impls::local_user::LocalUserOptionHelper,
newtypes::{CommunityId, InstanceId, PaginationCursor, PersonId, PostId},
source::{
local_user::LocalUser,
post::{post_keys as key, Post},
person::Person,
post::{post_actions_keys as pa_key, post_keys as key, Post, PostActions},
site::Site,
},
traits::{Crud, PaginationCursorBuilder},
@ -164,6 +165,44 @@ impl PostView {
.with_lemmy_type(LemmyErrorType::NotFound)
}
/// List all the read posts for your person, ordered by the read date.
pub async fn list_read(
pool: &mut DbPool<'_>,
my_person: &Person,
cursor_data: Option<PostActions>,
page_back: Option<bool>,
limit: Option<i64>,
) -> LemmyResult<Vec<PostView>> {
let conn = &mut get_conn(pool).await?;
let limit = limit_fetch(limit)?;
let query = PostView::joins(Some(my_person.id), my_person.instance_id)
.filter(post_actions::person_id.eq(my_person.id))
.filter(post_actions::read.is_not_null())
.filter(filter_blocked())
.select(PostView::as_select())
.limit(limit)
.into_boxed();
// Sorting by the read date
let paginated_query = paginate(query, SortDirection::Desc, cursor_data, None, page_back)
.then_order_by(pa_key::read)
// Tie breaker
.then_order_by(pa_key::post_id);
paginated_query
.load::<Self>(conn)
.await
.with_lemmy_type(LemmyErrorType::NotFound)
}
pub fn to_post_actions_cursor(&self) -> PaginationCursor {
// This needs a person and post
let prefixes_and_ids = [('P', self.creator.id.0), ('O', self.post.id.0)];
PaginationCursor::new(&prefixes_and_ids)
}
// TODO this function needs to be checked
/// Prefetches an upper bound to build the cursor before data.
pub async fn prefetch_cursor_before_data(
@ -988,34 +1027,33 @@ mod tests {
Ok(())
}
// TODO these need to get moved to the post_read_list
// #[test_context(Data)]
// #[tokio::test]
// #[serial]
// async fn post_listing_read_only(data: &mut Data) -> LemmyResult<()> {
// let pool = &data.pool();
// let pool = &mut pool.into();
#[test_context(Data)]
#[tokio::test]
#[serial]
async fn post_listing_read_only(data: &mut Data) -> LemmyResult<()> {
let pool = &data.pool();
let pool = &mut pool.into();
// // Only mark the bot post as read
// // The read_only should only show the bot post
// let post_read_form = PostReadForm::new(data.bot_post.id,
// data.tegan_local_user_view.person.id); PostActions::mark_as_read(pool,
// &post_read_form).await?;
// Mark the bot post, then the tags post as read
let bot_post_read_form =
PostReadForm::new(data.bot_post.id, data.tegan_local_user_view.person.id);
PostActions::mark_as_read(pool, &bot_post_read_form).await?;
// // Only read the post marked as read
// let read_read_post_listing = PostQuery {
// community_id: Some(data.community.id),
// read_only: Some(true),
// ..data.default_post_query()
// }
// .list(&data.site, pool)
// .await?;
let tag_post_read_form =
PostReadForm::new(data.post_with_tags.id, data.tegan_local_user_view.person.id);
PostActions::mark_as_read(pool, &tag_post_read_form).await?;
// // This should only include the bot post, not the one you created
// assert_eq!(vec![POST_BY_BOT], names(&read_read_post_listing));
let read_read_post_listing =
PostView::list_read(pool, &data.tegan_local_user_view.person, None, None, None).await?;
// Ok(())
// }
// This should be ordered from most recently read
assert_eq!(
vec![POST_WITH_TAGS, POST_BY_BOT],
names(&read_read_post_listing)
);
Ok(())
}
#[test_context(Data)]
#[tokio::test]

View file

@ -32,6 +32,7 @@ use lemmy_api::{
list_banned::list_banned_users,
list_logins::list_logins,
list_media::list_media,
list_read::list_person_read,
list_saved::list_person_saved,
login::login,
logout::logout,
@ -367,6 +368,7 @@ pub fn config(cfg: &mut ServiceConfig, rate_limit: &RateLimitCell) {
.route("/instance", post().to(user_block_instance)),
)
.route("/saved", get().to(list_person_saved))
.route("/read", get().to(list_person_read))
.route("/settings/save", put().to(save_user_settings))
// Account settings import / export have a strict rate limit
.service(