Add endpoint to get Liked / Disliked comments and posts. (#5616)

* 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 endpoint to list hidden posts.

- Fixes #5607

* Adding a first_id helper function.

* Fixing api_common

* Adding a person/liked endpoint for posts and comments.

- Fixes #4499

* Get rid of old liked_only filters.

* Formatting sql.

* Clippy

* Fixing view tests.

* SQL fmt

* 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

* Fixing clippy.

* Adding person_local column to post_actions and comment_actions

* Fixing pg_format

* Fixing pg_format 2

* Revert "Adding person_local column to post_actions and comment_actions"

This reverts commit e5145d8b07.

* Adding local person check to liked and saved combined.

* Formatting sql.

* Adding local check to migration.

* Addressing PR comments

* Try to add versioned pg_formatter 1

* Try to add versioned pg_formatter 2

* Try to add versioned pg_formatter 3

* Try to add versioned pg_formatter 4

* Moving back to correct location.

---------

Co-authored-by: Felix Ableitner <me@nutomic.com>
Co-authored-by: dullbananas <dull.bananas0@gmail.com>
This commit is contained in:
Dessalines 2025-06-02 05:15:27 -04:00 committed by GitHub
parent 7504738de0
commit 9250460b11
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
71 changed files with 999 additions and 343 deletions

View file

@ -61,10 +61,10 @@ steps:
- event: pull_request
sql_fmt:
image: debian:bookworm
image: alpine:3
commands:
- apt-get update
- apt-get --yes --no-install-recommends --no-install-suggests install pgformatter
- apk add perl make bash
- ./scripts/alpine_install_pg_formatter.sh
- ./scripts/sql_format_check.sh
when:
- event: pull_request

22
Cargo.lock generated
View file

@ -3131,6 +3131,7 @@ dependencies = [
"lemmy_db_views_local_user",
"lemmy_db_views_modlog_combined",
"lemmy_db_views_person",
"lemmy_db_views_person_liked_combined",
"lemmy_db_views_person_saved_combined",
"lemmy_db_views_post",
"lemmy_db_views_registration_applications",
@ -3599,6 +3600,27 @@ dependencies = [
"ts-rs",
]
[[package]]
name = "lemmy_db_views_person_liked_combined"
version = "1.0.0-alpha.5"
dependencies = [
"diesel",
"diesel-async",
"i-love-jesus",
"lemmy_db_schema",
"lemmy_db_schema_file",
"lemmy_db_views_comment",
"lemmy_db_views_local_user",
"lemmy_db_views_post",
"lemmy_utils",
"pretty_assertions",
"serde",
"serde_with",
"serial_test",
"tokio",
"ts-rs",
]
[[package]]
name = "lemmy_db_views_person_saved_combined"
version = "1.0.0-alpha.5"

View file

@ -67,6 +67,7 @@ members = [
"crates/db_views/modlog_combined",
"crates/db_views/person_content_combined",
"crates/db_views/person_saved_combined",
"crates/db_views/person_liked_combined",
"crates/db_views/report_combined",
"crates/db_views/search_combined",
"crates/db_views/site",
@ -132,6 +133,7 @@ lemmy_db_views_inbox_combined = { version = "=1.0.0-alpha.5", path = "./crates/d
lemmy_db_views_modlog_combined = { version = "=1.0.0-alpha.5", path = "./crates/db_views/modlog_combined" }
lemmy_db_views_person_content_combined = { version = "=1.0.0-alpha.5", path = "./crates/db_views/person_content_combined" }
lemmy_db_views_person_saved_combined = { version = "=1.0.0-alpha.5", path = "./crates/db_views/person_saved_combined" }
lemmy_db_views_person_liked_combined = { version = "=1.0.0-alpha.5", path = "./crates/db_views/person_liked_combined" }
lemmy_db_views_report_combined = { version = "=1.0.0-alpha.5", path = "./crates/db_views/report_combined" }
lemmy_db_views_search_combined = { version = "=1.0.0-alpha.5", path = "./crates/db_views/search_combined" }
lemmy_db_views_site = { version = "=1.0.0-alpha.5", path = "./crates/db_views/site" }

View file

@ -32,6 +32,7 @@ lemmy_db_views_local_image = { workspace = true, features = ["full"] }
lemmy_db_views_inbox_combined = { workspace = true, features = ["full"] }
lemmy_db_views_modlog_combined = { workspace = true, features = ["full"] }
lemmy_db_views_person_saved_combined = { workspace = true, features = ["full"] }
lemmy_db_views_person_liked_combined = { workspace = true, features = ["full"] }
lemmy_db_views_report_combined = { workspace = true, features = ["full"] }
lemmy_db_views_site = { workspace = true, features = ["full"] }
lemmy_db_views_registration_applications = { workspace = true, features = [

View file

@ -0,0 +1,43 @@
use activitypub_federation::config::Data;
use actix_web::web::{Json, Query};
use lemmy_api_common::context::LemmyContext;
use lemmy_db_schema::traits::PaginationCursorBuilder;
use lemmy_db_views_local_user::LocalUserView;
use lemmy_db_views_person_liked_combined::{
impls::PersonLikedCombinedQuery,
ListPersonLiked,
ListPersonLikedResponse,
PersonLikedCombinedView,
};
use lemmy_utils::error::LemmyResult;
pub async fn list_person_liked(
data: Query<ListPersonLiked>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> LemmyResult<Json<ListPersonLikedResponse>> {
let cursor_data = if let Some(cursor) = &data.page_cursor {
Some(PersonLikedCombinedView::from_cursor(cursor, &mut context.pool()).await?)
} else {
None
};
let liked = PersonLikedCombinedQuery {
type_: data.type_,
like_type: data.like_type,
cursor_data,
page_back: data.page_back,
limit: data.limit,
}
.list(&mut context.pool(), &local_user_view)
.await?;
let next_page = liked.last().map(PaginationCursorBuilder::to_cursor);
let prev_page = liked.first().map(PaginationCursorBuilder::to_cursor);
Ok(Json(ListPersonLikedResponse {
liked,
next_page,
prev_page,
}))
}

View file

@ -7,6 +7,7 @@ pub mod donation_dialog_shown;
pub mod generate_totp_secret;
pub mod get_captcha;
pub mod list_hidden;
pub mod list_liked;
pub mod list_logins;
pub mod list_media;
pub mod list_read;

View file

@ -141,10 +141,6 @@ pub struct GetComments {
pub post_id: Option<PostId>,
#[cfg_attr(feature = "full", ts(optional))]
pub parent_id: Option<CommentId>,
#[cfg_attr(feature = "full", ts(optional))]
pub liked_only: Option<bool>,
#[cfg_attr(feature = "full", ts(optional))]
pub disliked_only: Option<bool>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]

View file

@ -96,10 +96,6 @@ pub struct GetPosts {
#[cfg_attr(feature = "full", ts(optional))]
pub community_name: Option<String>,
#[cfg_attr(feature = "full", ts(optional))]
pub liked_only: Option<bool>,
#[cfg_attr(feature = "full", ts(optional))]
pub disliked_only: Option<bool>,
#[cfg_attr(feature = "full", ts(optional))]
pub show_hidden: Option<bool>,
/// If true, then show the read posts (even if your user setting is to hide them)
#[cfg_attr(feature = "full", ts(optional))]

View file

@ -5,7 +5,7 @@ use actix_web::web::{Json, Query};
use lemmy_api_common::{
comment::{GetComments, GetCommentsResponse, GetCommentsSlimResponse},
context::LemmyContext,
utils::{check_conflicting_like_filters, check_private_instance},
utils::check_private_instance,
};
use lemmy_apub_objects::objects::community::ApubCommunity;
use lemmy_db_schema::{
@ -50,11 +50,6 @@ async fn list_comments_common(
));
let time_range_seconds = data.time_range_seconds;
let max_depth = data.max_depth;
let liked_only = data.liked_only;
let disliked_only = data.disliked_only;
check_conflicting_like_filters(liked_only, disliked_only)?;
let limit = data.limit;
let parent_id = data.parent_id;
@ -88,8 +83,6 @@ async fn list_comments_common(
sort,
time_range_seconds,
max_depth,
liked_only,
disliked_only,
community_id,
parent_path,
post_id,

View file

@ -11,7 +11,7 @@ use actix_web::web::{Json, Query};
use lemmy_api_common::{
context::LemmyContext,
post::{GetPosts, GetPostsResponse},
utils::{check_conflicting_like_filters, check_private_instance},
utils::check_private_instance,
};
use lemmy_apub_objects::objects::community::ApubCommunity;
use lemmy_db_schema::{
@ -50,10 +50,6 @@ pub async fn list_posts(
let hide_media = data.hide_media;
let no_comments_only = data.no_comments_only;
let liked_only = data.liked_only;
let disliked_only = data.disliked_only;
check_conflicting_like_filters(liked_only, disliked_only)?;
let local_user = local_user_view.as_ref().map(|u| &u.local_user);
let listing_type = Some(listing_type_with_default(
data.type_,
@ -91,8 +87,6 @@ pub async fn list_posts(
sort,
time_range_seconds,
community_id,
liked_only,
disliked_only,
limit,
show_hidden,
show_read,

View file

@ -158,6 +158,19 @@ pub enum PostFeatureType {
Community,
}
#[derive(
EnumString, Display, Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash,
)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
/// The like_type for a persons liked content.
pub enum LikeType {
#[default]
All,
LikedOnly,
DislikedOnly,
}
/// Wrapper for assert_eq! macro. Checks that vec matches the given length, and prints the
/// vec on failure.
#[macro_export]

View file

@ -211,6 +211,11 @@ pub struct PersonContentCombinedId(i32);
/// The person saved combined id
pub struct PersonSavedCombinedId(i32);
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Default)]
#[cfg_attr(feature = "full", derive(DieselNewType))]
/// The person liked combined id
pub struct PersonLikedCombinedId(i32);
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Default)]
#[cfg_attr(feature = "full", derive(DieselNewType))]
pub struct ModlogCombinedId(i32);

View file

@ -1,6 +1,7 @@
pub mod inbox;
pub mod modlog;
pub mod person_content;
pub mod person_liked;
pub mod person_saved;
pub mod report;
pub mod search;

View file

@ -0,0 +1,27 @@
use crate::newtypes::{CommentId, PersonId, PersonLikedCombinedId, PostId};
use chrono::{DateTime, Utc};
#[cfg(feature = "full")]
use i_love_jesus::CursorKeysModule;
#[cfg(feature = "full")]
use lemmy_db_schema_file::schema::person_liked_combined;
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
#[skip_serializing_none]
#[derive(PartialEq, Eq, Serialize, Deserialize, Debug, Clone)]
#[cfg_attr(
feature = "full",
derive(Identifiable, Queryable, Selectable, CursorKeysModule)
)]
#[cfg_attr(feature = "full", diesel(table_name = person_liked_combined))]
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
#[cfg_attr(feature = "full", cursor_keys_module(name = person_liked_combined_keys))]
/// A combined person_liked table.
pub struct PersonLikedCombined {
pub id: PersonLikedCombinedId,
pub liked: DateTime<Utc>,
pub like_score: i16,
pub person_id: PersonId,
pub post_id: Option<PostId>,
pub comment_id: Option<CommentId>,
}

View file

@ -67,17 +67,14 @@ CREATE FUNCTION r.parent_comment_ids (path ltree)
RETURNS SETOF int
LANGUAGE sql
IMMUTABLE parallel safe
BEGIN
ATOMIC
BEGIN ATOMIC
SELECT
comment_id::int
FROM
string_to_table (ltree2text (path), '.') AS comment_id
-- Skip first and last
LIMIT (nlevel (path) - 2) OFFSET 1;
END;
CALL r.create_triggers ('comment', $$
BEGIN
-- Prevent infinite recursion
@ -86,9 +83,7 @@ BEGIN
count(*)
FROM select_old_and_new_rows AS old_and_new_rows) = 0 THEN
RETURN NULL;
END IF;
UPDATE
person AS a
SET
@ -106,7 +101,6 @@ FROM (
WHERE
a.id = diff.creator_id
AND diff.comment_count != 0;
UPDATE
comment AS a
SET
@ -144,7 +138,6 @@ FROM (
WHERE
a.id = diff.parent_id
AND diff.child_count != 0;
UPDATE
post AS a
SET
@ -176,7 +169,6 @@ WHERE
GREATEST (a.newest_comment_time_necro, diff.newest_comment_time_necro)) != (0,
a.newest_comment_time,
a.newest_comment_time_necro);
UPDATE
local_site AS a
SET
@ -191,13 +183,9 @@ FROM (
AND (comment).local) AS diff
WHERE
diff.comments != 0;
RETURN NULL;
END;
$$);
CALL r.create_triggers ('post', $$
BEGIN
UPDATE
@ -214,7 +202,6 @@ BEGIN
WHERE
a.id = diff.creator_id
AND diff.post_count != 0;
UPDATE
community AS a
SET
@ -236,7 +223,6 @@ WHERE
AND (diff.posts,
diff.comments) != (0,
0);
UPDATE
local_site AS a
SET
@ -251,13 +237,9 @@ FROM (
AND (post).local) AS diff
WHERE
diff.posts != 0;
RETURN NULL;
END;
$$);
CALL r.create_triggers ('community', $$
BEGIN
UPDATE
@ -273,13 +255,9 @@ BEGIN
AND (community).local) AS diff
WHERE
diff.communities != 0;
RETURN NULL;
END;
$$);
CALL r.create_triggers ('local_user', $$
BEGIN
UPDATE
@ -293,13 +271,9 @@ BEGIN
WHERE (local_user).accepted_application) AS diff
WHERE
diff.users != 0;
RETURN NULL;
END;
$$);
-- Count subscribers for communities.
-- subscribers should be updated only when a local community is followed by a local or remote person.
-- subscribers_local should be updated only when a local person follows a local or remote community.
@ -319,13 +293,9 @@ BEGIN
WHERE
a.id = diff.community_id
AND (diff.subscribers, diff.subscribers_local) != (0, 0);
RETURN NULL;
END;
$$);
CALL r.create_triggers ('post_report', $$
BEGIN
UPDATE
@ -339,13 +309,9 @@ BEGIN
FROM select_old_and_new_rows AS old_and_new_rows GROUP BY (post_report).post_id) AS diff
WHERE (diff.report_count, diff.unresolved_report_count) != (0, 0)
AND a.id = diff.post_id;
RETURN NULL;
END;
$$);
CALL r.create_triggers ('comment_report', $$
BEGIN
UPDATE
@ -359,13 +325,9 @@ BEGIN
FROM select_old_and_new_rows AS old_and_new_rows GROUP BY (comment_report).comment_id) AS diff
WHERE (diff.report_count, diff.unresolved_report_count) != (0, 0)
AND a.id = diff.comment_id;
RETURN NULL;
END;
$$);
CALL r.create_triggers ('community_report', $$
BEGIN
UPDATE
@ -378,13 +340,9 @@ BEGIN
FROM select_old_and_new_rows AS old_and_new_rows GROUP BY (community_report).community_id) AS diff
WHERE (diff.report_count, diff.unresolved_report_count) != (0, 0)
AND a.id = diff.community_id;
RETURN NULL;
END;
$$);
-- Change the order of some cascading deletions to make deletion triggers run before the deletion of rows that the triggers need to read
CREATE FUNCTION r.delete_follow_before_person ()
RETURNS TRIGGER
@ -396,12 +354,10 @@ BEGIN
RETURN OLD;
END;
$$;
CREATE TRIGGER delete_follow
BEFORE DELETE ON person
FOR EACH ROW
EXECUTE FUNCTION r.delete_follow_before_person ();
-- Triggers that change values before insert or update
CREATE FUNCTION r.comment_change_values ()
RETURNS TRIGGER
@ -421,12 +377,10 @@ BEGIN
RETURN NEW;
END
$$;
CREATE TRIGGER change_values
BEFORE INSERT OR UPDATE ON comment
FOR EACH ROW
EXECUTE FUNCTION r.comment_change_values ();
CREATE FUNCTION r.post_change_values ()
RETURNS TRIGGER
LANGUAGE plpgsql
@ -442,12 +396,10 @@ BEGIN
RETURN NEW;
END
$$;
CREATE TRIGGER change_values
BEFORE INSERT ON post
FOR EACH ROW
EXECUTE FUNCTION r.post_change_values ();
CREATE FUNCTION r.private_message_change_values ()
RETURNS TRIGGER
LANGUAGE plpgsql
@ -460,12 +412,10 @@ BEGIN
RETURN NEW;
END
$$;
CREATE TRIGGER change_values
BEFORE INSERT ON private_message
FOR EACH ROW
EXECUTE FUNCTION r.private_message_change_values ();
-- Combined tables triggers
-- These insert (published, item_id) into X_combined tables
-- Reports (comment_report, post_report, private_message_report)
@ -491,15 +441,10 @@ BEGIN
table_name);
END;
$a$;
CALL r.create_report_combined_trigger ('post_report');
CALL r.create_report_combined_trigger ('comment_report');
CALL r.create_report_combined_trigger ('private_message_report');
CALL r.create_report_combined_trigger ('community_report');
-- person_content (comment, post)
CREATE PROCEDURE r.create_person_content_combined_trigger (table_name text)
LANGUAGE plpgsql
@ -523,14 +468,13 @@ BEGIN
table_name);
END;
$a$;
CALL r.create_person_content_combined_trigger ('post');
CALL r.create_person_content_combined_trigger ('comment');
-- person_saved (comment, post)
-- This one is a little different, because it triggers using x_actions.saved,
-- Rather than any row insert
-- TODO a hack because local is not currently on the post_view table
-- https://github.com/LemmyNet/lemmy/pull/5616#discussion_r2064219628
CREATE PROCEDURE r.create_person_saved_combined_trigger (table_name text)
LANGUAGE plpgsql
AS $a$
@ -571,11 +515,67 @@ BEGIN
table_name);
END;
$a$;
CALL r.create_person_saved_combined_trigger ('post');
CALL r.create_person_saved_combined_trigger ('comment');
-- person_liked (comment, post)
-- This one is a little different, because it triggers using x_actions.liked,
-- Rather than any row insert
-- TODO a hack because local is not currently on the post_view table
-- https://github.com/LemmyNet/lemmy/pull/5616#discussion_r2064219628
CREATE PROCEDURE r.create_person_liked_combined_trigger (table_name text)
LANGUAGE plpgsql
AS $a$
BEGIN
EXECUTE replace($b$ CREATE FUNCTION r.person_liked_combined_change_values_thing ( )
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
BEGIN
IF (TG_OP = 'DELETE') THEN
DELETE FROM person_liked_combined AS p
WHERE p.person_id = OLD.person_id
AND p.thing_id = OLD.thing_id;
ELSIF (TG_OP = 'INSERT') THEN
IF NEW.liked IS NOT NULL AND (
SELECT
local
FROM
person
WHERE
id = NEW.person_id) = TRUE THEN
INSERT INTO person_liked_combined (liked, like_score, person_id, thing_id)
VALUES (NEW.liked, NEW.like_score, NEW.person_id, NEW.thing_id);
END IF;
ELSIF (TG_OP = 'UPDATE') THEN
IF NEW.liked IS NOT NULL AND (
SELECT
local
FROM
person
WHERE
id = NEW.person_id) = TRUE THEN
INSERT INTO person_liked_combined (liked, like_score, person_id, thing_id)
VALUES (NEW.liked, NEW.like_score, NEW.person_id, NEW.thing_id);
-- If liked gets set as null, delete the row
ELSE
DELETE FROM person_liked_combined AS p
WHERE p.person_id = NEW.person_id
AND p.thing_id = NEW.thing_id;
END IF;
END IF;
RETURN NULL;
END $$;
CREATE TRIGGER person_liked_combined
AFTER INSERT OR DELETE OR UPDATE OF liked ON thing_actions
FOR EACH ROW
EXECUTE FUNCTION r.person_liked_combined_change_values_thing ( );
$b$,
'thing',
table_name);
END;
$a$;
CALL r.create_person_liked_combined_trigger ('post');
CALL r.create_person_liked_combined_trigger ('comment');
-- modlog: (17 tables)
-- admin_allow_instance
-- admin_block_instance
@ -616,41 +616,23 @@ BEGIN
table_name);
END;
$a$;
CALL r.create_modlog_combined_trigger ('admin_allow_instance');
CALL r.create_modlog_combined_trigger ('admin_block_instance');
CALL r.create_modlog_combined_trigger ('admin_purge_comment');
CALL r.create_modlog_combined_trigger ('admin_purge_community');
CALL r.create_modlog_combined_trigger ('admin_purge_person');
CALL r.create_modlog_combined_trigger ('admin_purge_post');
CALL r.create_modlog_combined_trigger ('mod_add');
CALL r.create_modlog_combined_trigger ('mod_add_community');
CALL r.create_modlog_combined_trigger ('mod_ban');
CALL r.create_modlog_combined_trigger ('mod_ban_from_community');
CALL r.create_modlog_combined_trigger ('mod_feature_post');
CALL r.create_modlog_combined_trigger ('mod_change_community_visibility');
CALL r.create_modlog_combined_trigger ('mod_lock_post');
CALL r.create_modlog_combined_trigger ('mod_remove_comment');
CALL r.create_modlog_combined_trigger ('mod_remove_community');
CALL r.create_modlog_combined_trigger ('mod_remove_post');
CALL r.create_modlog_combined_trigger ('mod_transfer_community');
-- Inbox: (replies, comment mentions, post mentions, and private_messages)
CREATE PROCEDURE r.create_inbox_combined_trigger (table_name text)
LANGUAGE plpgsql
@ -674,15 +656,10 @@ BEGIN
table_name);
END;
$a$;
CALL r.create_inbox_combined_trigger ('comment_reply');
CALL r.create_inbox_combined_trigger ('person_comment_mention');
CALL r.create_inbox_combined_trigger ('person_post_mention');
CALL r.create_inbox_combined_trigger ('private_message');
-- Prevent using delete instead of uplete on action tables
CREATE FUNCTION r.require_uplete ()
RETURNS TRIGGER
@ -695,32 +672,26 @@ BEGIN
RETURN NULL;
END
$$;
CREATE TRIGGER require_uplete
BEFORE DELETE ON comment_actions
FOR EACH STATEMENT
EXECUTE FUNCTION r.require_uplete ();
CREATE TRIGGER require_uplete
BEFORE DELETE ON community_actions
FOR EACH STATEMENT
EXECUTE FUNCTION r.require_uplete ();
CREATE TRIGGER require_uplete
BEFORE DELETE ON instance_actions
FOR EACH STATEMENT
EXECUTE FUNCTION r.require_uplete ();
CREATE TRIGGER require_uplete
BEFORE DELETE ON person_actions
FOR EACH STATEMENT
EXECUTE FUNCTION r.require_uplete ();
CREATE TRIGGER require_uplete
BEFORE DELETE ON post_actions
FOR EACH STATEMENT
EXECUTE FUNCTION r.require_uplete ();
-- search: (post, comment, community, person)
CREATE PROCEDURE r.create_search_combined_trigger (table_name text)
LANGUAGE plpgsql
@ -745,15 +716,10 @@ BEGIN
table_name);
END;
$a$;
CALL r.create_search_combined_trigger ('post');
CALL r.create_search_combined_trigger ('comment');
CALL r.create_search_combined_trigger ('community');
CALL r.create_search_combined_trigger ('person');
-- You also need to triggers to update the `score` column.
-- post | post::score
-- comment | comment_aggregates::score
@ -775,12 +741,10 @@ BEGIN
RETURN NULL;
END
$$;
CREATE TRIGGER search_combined_post_score
AFTER UPDATE OF score ON post
FOR EACH ROW
EXECUTE FUNCTION r.search_combined_post_score_update ();
-- Comment score
CREATE FUNCTION r.search_combined_comment_score_update ()
RETURNS TRIGGER
@ -796,12 +760,10 @@ BEGIN
RETURN NULL;
END
$$;
CREATE TRIGGER search_combined_comment_score
AFTER UPDATE OF score ON comment
FOR EACH ROW
EXECUTE FUNCTION r.search_combined_comment_score_update ();
-- Person score
CREATE FUNCTION r.search_combined_person_score_update ()
RETURNS TRIGGER
@ -817,12 +779,10 @@ BEGIN
RETURN NULL;
END
$$;
CREATE TRIGGER search_combined_person_score
AFTER UPDATE OF post_score ON person
FOR EACH ROW
EXECUTE FUNCTION r.search_combined_person_score_update ();
-- Community score
CREATE FUNCTION r.search_combined_community_score_update ()
RETURNS TRIGGER
@ -838,9 +798,7 @@ BEGIN
RETURN NULL;
END
$$;
CREATE TRIGGER search_combined_community_score
AFTER UPDATE OF users_active_month ON community
FOR EACH ROW
EXECUTE FUNCTION r.search_combined_community_score_update ();

View file

@ -814,6 +814,17 @@ diesel::table! {
}
}
diesel::table! {
person_liked_combined (id) {
id -> Int4,
liked -> Timestamptz,
like_score -> Int2,
person_id -> Int4,
post_id -> Nullable<Int4>,
comment_id -> Nullable<Int4>,
}
}
diesel::table! {
person_post_mention (id) {
id -> Int4,
@ -1167,6 +1178,9 @@ diesel::joinable!(person_comment_mention -> comment (comment_id));
diesel::joinable!(person_comment_mention -> person (recipient_id));
diesel::joinable!(person_content_combined -> comment (comment_id));
diesel::joinable!(person_content_combined -> post (post_id));
diesel::joinable!(person_liked_combined -> comment (comment_id));
diesel::joinable!(person_liked_combined -> person (person_id));
diesel::joinable!(person_liked_combined -> post (post_id));
diesel::joinable!(person_post_mention -> person (recipient_id));
diesel::joinable!(person_post_mention -> post (post_id));
diesel::joinable!(person_saved_combined -> comment (comment_id));
@ -1251,6 +1265,7 @@ diesel::allow_tables_to_appear_in_same_query!(
person_ban,
person_comment_mention,
person_content_combined,
person_liked_combined,
person_post_mention,
person_saved_combined,
post,

View file

@ -48,15 +48,7 @@ use lemmy_db_schema_file::{
CommunityVisibility,
ListingType,
},
schema::{
comment,
comment_actions,
community,
community_actions,
local_user_language,
person,
post,
},
schema::{comment, community, community_actions, local_user_language, person, post},
};
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
@ -163,9 +155,6 @@ pub struct CommentQuery<'a> {
pub post_id: Option<PostId>,
pub parent_path: Option<Ltree>,
pub local_user: Option<&'a LocalUser>,
// TODO get rid of liked / disliked_only
pub liked_only: Option<bool>,
pub disliked_only: Option<bool>,
pub max_depth: Option<i32>,
pub cursor_data: Option<Comment>,
pub page_back: Option<bool>,
@ -208,19 +197,6 @@ impl CommentQuery<'_> {
ListingType::ModeratorView => query.filter(community_actions::became_moderator.is_not_null()),
};
if let Some(my_id) = my_person_id {
let not_creator_filter = comment::creator_id.ne(my_id);
if o.liked_only.unwrap_or_default() {
query = query
.filter(not_creator_filter)
.filter(comment_actions::like_score.eq(1));
} else if o.disliked_only.unwrap_or_default() {
query = query
.filter(not_creator_filter)
.filter(comment_actions::like_score.eq(-1));
}
}
if !o.local_user.show_bot_accounts() {
query = query.filter(person::bot_account.eq(false));
};
@ -548,52 +524,6 @@ mod tests {
cleanup(data, pool).await
}
#[tokio::test]
#[serial]
async fn test_liked_only() -> LemmyResult<()> {
let pool = &build_db_pool_for_tests();
let pool = &mut pool.into();
let data = init_data(pool).await?;
// Unblock sara first
let timmy_unblocks_sara_form =
PersonBlockForm::new(data.timmy_local_user_view.person.id, data.sara_person.id);
PersonActions::unblock(pool, &timmy_unblocks_sara_form).await?;
// Like a new comment
let comment_like_form =
CommentLikeForm::new(data.timmy_local_user_view.person.id, data.comment_1.id, 1);
CommentActions::like(pool, &comment_like_form).await?;
let read_liked_comment_views = CommentQuery {
local_user: Some(&data.timmy_local_user_view.local_user),
liked_only: Some(true),
..Default::default()
}
.list(&data.site, pool)
.await?
.into_iter()
.map(|c| c.comment.content)
.collect::<Vec<String>>();
// Shouldn't include your own post, only other peoples
assert_eq!(data.comment_1.content, read_liked_comment_views[0]);
assert_length!(1, read_liked_comment_views);
let read_disliked_comment_views: Vec<CommentView> = CommentQuery {
local_user: Some(&data.timmy_local_user_view.local_user),
disliked_only: Some(true),
..Default::default()
}
.list(&data.site, pool)
.await?;
assert!(read_disliked_comment_views.is_empty());
cleanup(data, pool).await
}
#[tokio::test]
#[serial]
async fn test_comment_tree() -> LemmyResult<()> {

View file

@ -0,0 +1,47 @@
[package]
name = "lemmy_db_views_person_liked_combined"
version.workspace = true
edition.workspace = true
description.workspace = true
license.workspace = true
homepage.workspace = true
documentation.workspace = true
repository.workspace = true
[lib]
doctest = false
[lints]
workspace = true
[features]
full = [
"lemmy_utils",
"diesel",
"diesel-async",
"ts-rs",
"i-love-jesus",
"lemmy_db_schema/full",
"lemmy_db_schema_file/full",
"lemmy_db_views_comment/full",
"lemmy_db_views_post/full",
]
[dependencies]
lemmy_db_views_post = { workspace = true }
lemmy_db_views_comment = { workspace = true }
lemmy_db_views_local_user = { workspace = true }
lemmy_db_schema = { workspace = true }
lemmy_utils = { workspace = true, optional = true }
lemmy_db_schema_file = { workspace = true }
diesel = { workspace = true, optional = true }
diesel-async = { workspace = true, optional = true }
serde = { workspace = true }
serde_with = { workspace = true }
ts-rs = { workspace = true, optional = true }
i-love-jesus = { workspace = true, optional = true }
[dev-dependencies]
pretty_assertions = { workspace = true }
serial_test = { workspace = true }
tokio = { workspace = true }

View file

@ -0,0 +1,451 @@
use crate::{
CommentView,
LocalUserView,
PersonLikedCombinedView,
PersonLikedCombinedViewInternal,
PostView,
};
use diesel::{
BoolExpressionMethods,
ExpressionMethods,
JoinOnDsl,
NullableExpressionMethods,
QueryDsl,
SelectableHelper,
};
use diesel_async::RunQueryDsl;
use i_love_jesus::SortDirection;
use lemmy_db_schema::{
newtypes::{InstanceId, PaginationCursor, PersonId},
source::combined::person_liked::{person_liked_combined_keys as key, PersonLikedCombined},
traits::{InternalToCombinedView, PaginationCursorBuilder},
utils::{
get_conn,
limit_fetch,
paginate,
queries::{
community_join,
creator_community_actions_join,
creator_home_instance_actions_join,
creator_local_instance_actions_join,
creator_local_user_admin_join,
image_details_join,
my_comment_actions_join,
my_community_actions_join,
my_instance_actions_person_join,
my_local_user_admin_join,
my_person_actions_join,
my_post_actions_join,
},
DbPool,
},
LikeType,
PersonContentType,
};
use lemmy_db_schema_file::schema::{comment, person, person_liked_combined, post};
use lemmy_utils::error::{LemmyErrorType, LemmyResult};
#[derive(Default)]
pub struct PersonLikedCombinedQuery {
pub type_: Option<PersonContentType>,
pub like_type: Option<LikeType>,
pub cursor_data: Option<PersonLikedCombined>,
pub page_back: Option<bool>,
pub limit: Option<i64>,
}
impl PaginationCursorBuilder for PersonLikedCombinedView {
type CursorData = PersonLikedCombined;
fn to_cursor(&self) -> PaginationCursor {
let (prefix, id) = match &self {
PersonLikedCombinedView::Comment(v) => ('C', v.comment.id.0),
PersonLikedCombinedView::Post(v) => ('P', v.post.id.0),
};
PaginationCursor::new_single(prefix, id)
}
async fn from_cursor(
cursor: &PaginationCursor,
pool: &mut DbPool<'_>,
) -> LemmyResult<Self::CursorData> {
let conn = &mut get_conn(pool).await?;
let pids = cursor.prefixes_and_ids();
let (prefix, id) = pids
.as_slice()
.first()
.ok_or(LemmyErrorType::CouldntParsePaginationToken)?;
let mut query = person_liked_combined::table
.select(Self::CursorData::as_select())
.into_boxed();
query = match prefix {
'C' => query.filter(person_liked_combined::comment_id.eq(id)),
'P' => query.filter(person_liked_combined::post_id.eq(id)),
_ => return Err(LemmyErrorType::CouldntParsePaginationToken.into()),
};
let token = query.first(conn).await?;
Ok(token)
}
}
impl PersonLikedCombinedViewInternal {
#[diesel::dsl::auto_type(no_type_alias)]
pub(crate) fn joins(my_person_id: PersonId, local_instance_id: InstanceId) -> _ {
let item_creator = person::id;
let comment_join =
comment::table.on(person_liked_combined::comment_id.eq(comment::id.nullable()));
let post_join = post::table.on(
person_liked_combined::post_id
.eq(post::id.nullable())
.or(comment::post_id.eq(post::id)),
);
let item_creator_join = person::table.on(
comment::creator_id
.eq(item_creator)
// Need to filter out the post rows where the post_id given is null
// Otherwise you'll get duped post rows
.or(
post::creator_id
.eq(item_creator)
.and(person_liked_combined::post_id.is_not_null()),
),
);
let my_community_actions_join: my_community_actions_join =
my_community_actions_join(Some(my_person_id));
let my_post_actions_join: my_post_actions_join = my_post_actions_join(Some(my_person_id));
let my_comment_actions_join: my_comment_actions_join =
my_comment_actions_join(Some(my_person_id));
let my_local_user_admin_join: my_local_user_admin_join =
my_local_user_admin_join(Some(my_person_id));
let my_instance_actions_person_join: my_instance_actions_person_join =
my_instance_actions_person_join(Some(my_person_id));
let my_person_actions_join: my_person_actions_join = my_person_actions_join(Some(my_person_id));
let creator_local_instance_actions_join: creator_local_instance_actions_join =
creator_local_instance_actions_join(local_instance_id);
person_liked_combined::table
.left_join(comment_join)
.inner_join(post_join)
.inner_join(item_creator_join)
.inner_join(community_join())
.left_join(creator_community_actions_join())
.left_join(my_local_user_admin_join)
.left_join(creator_local_user_admin_join())
.left_join(my_community_actions_join)
.left_join(my_instance_actions_person_join)
.left_join(creator_home_instance_actions_join())
.left_join(creator_local_instance_actions_join)
.left_join(my_post_actions_join)
.left_join(my_person_actions_join)
.left_join(my_comment_actions_join)
.left_join(image_details_join())
}
}
impl PersonLikedCombinedQuery {
pub async fn list(
self,
pool: &mut DbPool<'_>,
user: &LocalUserView,
) -> LemmyResult<Vec<PersonLikedCombinedView>> {
let my_person_id = user.local_user.person_id;
let local_instance_id = user.person.instance_id;
let conn = &mut get_conn(pool).await?;
let limit = limit_fetch(self.limit)?;
let mut query = PersonLikedCombinedViewInternal::joins(my_person_id, local_instance_id)
.filter(person_liked_combined::person_id.eq(my_person_id))
.select(PersonLikedCombinedViewInternal::as_select())
.limit(limit)
.into_boxed();
if let Some(type_) = self.type_ {
query = match type_ {
PersonContentType::All => query,
PersonContentType::Comments => {
query.filter(person_liked_combined::comment_id.is_not_null())
}
PersonContentType::Posts => query.filter(person_liked_combined::post_id.is_not_null()),
}
}
if let Some(like_type) = self.like_type {
query = match like_type {
LikeType::All => query,
LikeType::LikedOnly => query.filter(person_liked_combined::like_score.eq(1)),
LikeType::DislikedOnly => query.filter(person_liked_combined::like_score.eq(-1)),
}
}
// Sorting by liked desc
let paginated_query = paginate(
query,
SortDirection::Desc,
self.cursor_data,
None,
self.page_back,
)
.then_order_by(key::liked)
// Tie breaker
.then_order_by(key::id);
let res = paginated_query
.load::<PersonLikedCombinedViewInternal>(conn)
.await?;
// Map the query results to the enum
let out = res
.into_iter()
.filter_map(InternalToCombinedView::map_to_enum)
.collect();
Ok(out)
}
}
impl InternalToCombinedView for PersonLikedCombinedViewInternal {
type CombinedView = PersonLikedCombinedView;
fn map_to_enum(self) -> Option<Self::CombinedView> {
// Use for a short alias
let v = self;
if let Some(comment) = v.comment {
Some(PersonLikedCombinedView::Comment(CommentView {
comment,
post: v.post,
community: v.community,
creator: v.item_creator,
community_actions: v.community_actions,
comment_actions: v.comment_actions,
person_actions: v.person_actions,
instance_actions: v.instance_actions,
creator_home_instance_actions: v.creator_home_instance_actions,
creator_local_instance_actions: v.creator_local_instance_actions,
creator_community_actions: v.creator_community_actions,
creator_is_admin: v.item_creator_is_admin,
post_tags: v.post_tags,
can_mod: v.can_mod,
creator_banned: v.creator_banned,
}))
} else {
Some(PersonLikedCombinedView::Post(PostView {
post: v.post,
community: v.community,
creator: v.item_creator,
image_details: v.image_details,
community_actions: v.community_actions,
post_actions: v.post_actions,
person_actions: v.person_actions,
instance_actions: v.instance_actions,
creator_home_instance_actions: v.creator_home_instance_actions,
creator_local_instance_actions: v.creator_local_instance_actions,
creator_community_actions: v.creator_community_actions,
creator_is_admin: v.item_creator_is_admin,
tags: v.post_tags,
can_mod: v.can_mod,
creator_banned: v.creator_banned,
}))
}
}
}
#[cfg(test)]
#[expect(clippy::indexing_slicing)]
mod tests {
use crate::{impls::PersonLikedCombinedQuery, LocalUserView, PersonLikedCombinedView};
use lemmy_db_schema::{
source::{
comment::{Comment, CommentActions, CommentInsertForm, CommentLikeForm},
community::{Community, CommunityInsertForm},
instance::Instance,
local_user::{LocalUser, LocalUserInsertForm},
person::{Person, PersonInsertForm},
post::{Post, PostActions, PostInsertForm, PostLikeForm},
},
traits::{Crud, Likeable},
utils::{build_db_pool_for_tests, DbPool},
LikeType,
};
use lemmy_utils::error::LemmyResult;
use pretty_assertions::assert_eq;
use serial_test::serial;
struct Data {
instance: Instance,
timmy: Person,
timmy_view: LocalUserView,
sara: Person,
timmy_post: Post,
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 timmy_local_user_form = LocalUserInsertForm::test_form(timmy.id);
let timmy_local_user = LocalUser::create(pool, &timmy_local_user_form, vec![]).await?;
let timmy_view = LocalUserView {
local_user: timmy_local_user,
person: timmy.clone(),
instance_actions: None,
};
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,
timmy_view,
sara,
timmy_post,
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 liked
let timmy_liked = PersonLikedCombinedQuery::default()
.list(pool, &data.timmy_view)
.await?;
assert_eq!(0, timmy_liked.len());
// Like a few things
let like_sara_comment_2 = CommentLikeForm::new(data.timmy.id, data.sara_comment_2.id, 1);
CommentActions::like(pool, &like_sara_comment_2).await?;
let dislike_sara_comment = CommentLikeForm::new(data.timmy.id, data.sara_comment.id, -1);
CommentActions::like(pool, &dislike_sara_comment).await?;
let post_like_form = PostLikeForm::new(data.timmy_post.id, data.timmy.id, 1);
PostActions::like(pool, &post_like_form).await?;
let timmy_liked_all = PersonLikedCombinedQuery::default()
.list(pool, &data.timmy_view)
.await?;
assert_eq!(3, timmy_liked_all.len());
// Make sure the types and order are correct
if let PersonLikedCombinedView::Post(v) = &timmy_liked_all[0] {
assert_eq!(data.timmy_post.id, v.post.id);
assert_eq!(data.timmy.id, v.post.creator_id);
assert_eq!(Some(1), v.post_actions.as_ref().and_then(|l| l.like_score));
} else {
panic!("wrong type");
}
if let PersonLikedCombinedView::Comment(v) = &timmy_liked_all[1] {
assert_eq!(data.sara_comment.id, v.comment.id);
assert_eq!(data.sara.id, v.comment.creator_id);
assert_eq!(
Some(-1),
v.comment_actions.as_ref().and_then(|l| l.like_score)
);
} else {
panic!("wrong type");
}
if let PersonLikedCombinedView::Comment(v) = &timmy_liked_all[2] {
assert_eq!(data.sara_comment_2.id, v.comment.id);
assert_eq!(data.sara.id, v.comment.creator_id);
assert_eq!(
Some(1),
v.comment_actions.as_ref().and_then(|l| l.like_score)
);
} else {
panic!("wrong type");
}
let timmy_disliked = PersonLikedCombinedQuery {
like_type: Some(LikeType::DislikedOnly),
..PersonLikedCombinedQuery::default()
}
.list(pool, &data.timmy_view)
.await?;
assert_eq!(1, timmy_disliked.len());
if let PersonLikedCombinedView::Comment(v) = &timmy_disliked[0] {
assert_eq!(data.sara_comment.id, v.comment.id);
assert_eq!(data.sara.id, v.comment.creator_id);
assert_eq!(
Some(-1),
v.comment_actions.as_ref().and_then(|l| l.like_score)
);
} else {
panic!("wrong type");
}
// Try unliking 2 things
CommentActions::remove_like(pool, data.timmy.id, data.sara_comment.id).await?;
PostActions::remove_like(pool, data.timmy.id, data.timmy_post.id).await?;
let timmy_likes_removed = PersonLikedCombinedQuery::default()
.list(pool, &data.timmy_view)
.await?;
assert_eq!(1, timmy_likes_removed.len());
if let PersonLikedCombinedView::Comment(v) = &timmy_likes_removed[0] {
assert_eq!(data.sara_comment_2.id, v.comment.id);
assert_eq!(data.sara.id, v.comment.creator_id);
} else {
panic!("wrong type");
}
cleanup(data, pool).await?;
Ok(())
}
}

View file

@ -0,0 +1,152 @@
use lemmy_db_schema::{
newtypes::PaginationCursor,
source::{
combined::person_liked::PersonLikedCombined,
comment::{Comment, CommentActions},
community::{Community, CommunityActions},
images::ImageDetails,
instance::InstanceActions,
person::{Person, PersonActions},
post::{Post, PostActions},
tag::TagsView,
},
LikeType,
PersonContentType,
};
use lemmy_db_views_comment::CommentView;
use lemmy_db_views_post::PostView;
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
#[cfg(feature = "full")]
use {
diesel::{dsl::Nullable, NullableExpressionMethods, Queryable, Selectable},
lemmy_db_schema::{
utils::queries::{
creator_banned,
creator_community_actions_select,
creator_home_instance_actions_select,
creator_is_admin,
creator_local_instance_actions_select,
local_user_can_mod,
post_tags_fragment,
},
CreatorCommunityActionsAllColumnsTuple,
CreatorHomeInstanceActionsAllColumnsTuple,
CreatorLocalInstanceActionsAllColumnsTuple,
},
lemmy_db_views_local_user::LocalUserView,
ts_rs::TS,
};
#[cfg(feature = "full")]
pub mod impls;
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
#[cfg_attr(feature = "full", derive(Queryable, Selectable))]
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
/// A combined person_saved view
pub(crate) struct PersonLikedCombinedViewInternal {
#[cfg_attr(feature = "full", diesel(embed))]
pub person_liked_combined: PersonLikedCombined,
#[cfg_attr(feature = "full", diesel(embed))]
pub comment: Option<Comment>,
#[cfg_attr(feature = "full", diesel(embed))]
pub post: Post,
#[cfg_attr(feature = "full", diesel(embed))]
pub item_creator: Person,
#[cfg_attr(feature = "full", diesel(embed))]
pub community: Community,
#[cfg_attr(feature = "full",
diesel(
select_expression_type = Nullable<CreatorCommunityActionsAllColumnsTuple>,
select_expression = creator_community_actions_select().nullable()
)
)]
pub creator_community_actions: Option<CommunityActions>,
#[cfg_attr(feature = "full", diesel(embed))]
pub community_actions: Option<CommunityActions>,
#[cfg_attr(feature = "full", diesel(embed))]
pub instance_actions: Option<InstanceActions>,
#[cfg_attr(feature = "full", diesel(
select_expression_type = Nullable<CreatorHomeInstanceActionsAllColumnsTuple>,
select_expression = creator_home_instance_actions_select()))]
pub creator_home_instance_actions: Option<InstanceActions>,
#[cfg_attr(feature = "full", diesel(
select_expression_type = Nullable<CreatorLocalInstanceActionsAllColumnsTuple>,
select_expression = creator_local_instance_actions_select()))]
pub creator_local_instance_actions: Option<InstanceActions>,
#[cfg_attr(feature = "full", diesel(embed))]
pub post_actions: Option<PostActions>,
#[cfg_attr(feature = "full", diesel(embed))]
pub person_actions: Option<PersonActions>,
#[cfg_attr(feature = "full", diesel(embed))]
pub comment_actions: Option<CommentActions>,
#[cfg_attr(feature = "full", diesel(embed))]
pub image_details: Option<ImageDetails>,
#[cfg_attr(feature = "full",
diesel(
select_expression = creator_is_admin()
)
)]
pub item_creator_is_admin: bool,
#[cfg_attr(feature = "full",
diesel(
select_expression = post_tags_fragment()
)
)]
pub post_tags: TagsView,
#[cfg_attr(feature = "full",
diesel(
select_expression = local_user_can_mod()
)
)]
pub can_mod: bool,
#[cfg_attr(feature = "full",
diesel(
select_expression = creator_banned()
)
)]
pub creator_banned: bool,
}
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
// Use serde's internal tagging, to work easier with javascript libraries
#[serde(tag = "type_")]
pub enum PersonLikedCombinedView {
Post(PostView),
Comment(CommentView),
}
#[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 liked / disliked posts
pub struct ListPersonLiked {
#[cfg_attr(feature = "full", ts(optional))]
pub type_: Option<PersonContentType>,
#[cfg_attr(feature = "full", ts(optional))]
pub like_type: Option<LikeType>,
#[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))]
/// Your liked posts response.
pub struct ListPersonLikedResponse {
pub liked: Vec<PersonLikedCombinedView>,
/// 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>,
}

View file

@ -23,6 +23,8 @@ full = [
"i-love-jesus",
"lemmy_db_schema/full",
"lemmy_db_schema_file/full",
"lemmy_db_views_post/full",
"lemmy_db_views_comment/full",
]
[dependencies]

View file

@ -241,8 +241,6 @@ pub struct PostQuery<'a> {
pub time_range_seconds: Option<i32>,
pub community_id: Option<CommunityId>,
pub local_user: Option<&'a LocalUser>,
pub liked_only: Option<bool>,
pub disliked_only: Option<bool>,
pub show_hidden: Option<bool>,
pub show_read: Option<bool>,
pub show_nsfw: Option<bool>,
@ -396,20 +394,6 @@ impl PostQuery<'_> {
));
}
// TODO move liked only elsewhere
if let Some(my_id) = my_person_id {
let not_creator_filter = post::creator_id.ne(my_id);
if o.liked_only.unwrap_or_default() {
query = query
.filter(not_creator_filter)
.filter(post_actions::like_score.eq(1));
} else if o.disliked_only.unwrap_or_default() {
query = query
.filter(not_creator_filter)
.filter(post_actions::like_score.eq(-1));
}
};
query = o.local_user.visible_communities_only(query);
query = query.filter(
post::federation_pending
@ -1019,48 +1003,6 @@ mod tests {
Ok(())
}
#[test_context(Data)]
#[tokio::test]
#[serial]
async fn post_listing_liked_only(data: &mut Data) -> LemmyResult<()> {
let pool = &data.pool();
let pool = &mut pool.into();
// Like both the bot post, and your own
// The liked_only should not show your own post
let post_like_form = PostLikeForm::new(data.post.id, data.tegan_local_user_view.person.id, 1);
PostActions::like(pool, &post_like_form).await?;
let bot_post_like_form =
PostLikeForm::new(data.bot_post.id, data.tegan_local_user_view.person.id, 1);
PostActions::like(pool, &bot_post_like_form).await?;
// Read the liked only
let read_liked_post_listing = PostQuery {
community_id: Some(data.community.id),
liked_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_liked_post_listing));
let read_disliked_post_listing = PostQuery {
community_id: Some(data.community.id),
disliked_only: Some(true),
..data.default_post_query()
}
.list(&data.site, pool)
.await?;
// Should be no posts
assert_eq!(read_disliked_post_listing, vec![]);
Ok(())
}
#[test_context(Data)]
#[tokio::test]
#[serial]

View file

@ -49,7 +49,7 @@ with all_post AS (
NULL
END) AS upvotes,
count(
CASE WHEN pl.score = - 1 THEN
CASE WHEN pl.score = -1 THEN
1
ELSE
NULL

View file

@ -41,7 +41,7 @@ with all_comment AS (
NULL
END) AS upvotes,
count(
CASE WHEN cl.score = - 1 THEN
CASE WHEN cl.score = -1 THEN
1
ELSE
NULL

View file

@ -121,7 +121,7 @@ with all_post AS (
NULL
END) AS upvotes,
count(
CASE WHEN pl.score = - 1 THEN
CASE WHEN pl.score = -1 THEN
1
ELSE
NULL
@ -220,7 +220,7 @@ with all_comment AS (
NULL
END) AS upvotes,
count(
CASE WHEN cl.score = - 1 THEN
CASE WHEN cl.score = -1 THEN
1
ELSE
NULL

View file

@ -125,7 +125,7 @@ with all_post AS (
NULL
END) AS upvotes,
count(
CASE WHEN pl.score = - 1 THEN
CASE WHEN pl.score = -1 THEN
1
ELSE
NULL
@ -228,7 +228,7 @@ with all_comment AS (
NULL
END) AS upvotes,
count(
CASE WHEN cl.score = - 1 THEN
CASE WHEN cl.score = -1 THEN
1
ELSE
NULL

View file

@ -132,7 +132,7 @@ with all_post AS (
NULL
END) AS upvotes,
count(
CASE WHEN pl.score = - 1 THEN
CASE WHEN pl.score = -1 THEN
1
ELSE
NULL

View file

@ -139,7 +139,7 @@ with all_post AS (
NULL
END) AS upvotes,
count(
CASE WHEN pl.score = - 1 THEN
CASE WHEN pl.score = -1 THEN
1
ELSE
NULL

View file

@ -55,7 +55,7 @@ with all_post AS (
NULL
END) AS upvotes,
count(
CASE WHEN pl.score = - 1 THEN
CASE WHEN pl.score = -1 THEN
1
ELSE
NULL

View file

@ -70,7 +70,7 @@ with all_post AS (
NULL
END) AS upvotes,
count(
CASE WHEN pl.score = - 1 THEN
CASE WHEN pl.score = -1 THEN
1
ELSE
NULL

View file

@ -76,7 +76,7 @@ with all_post AS (
NULL
END) AS upvotes,
count(
CASE WHEN pl.score = - 1 THEN
CASE WHEN pl.score = -1 THEN
1
ELSE
NULL

View file

@ -122,7 +122,7 @@ with all_post AS (
NULL
END) AS upvotes,
count(
CASE WHEN pl.score = - 1 THEN
CASE WHEN pl.score = -1 THEN
1
ELSE
NULL

View file

@ -124,7 +124,7 @@ with all_post AS (
NULL
END) AS upvotes,
count(
CASE WHEN pl.score = - 1 THEN
CASE WHEN pl.score = -1 THEN
1
ELSE
NULL
@ -299,7 +299,7 @@ with all_comment AS (
NULL
END) AS upvotes,
count(
CASE WHEN cl.score = - 1 THEN
CASE WHEN cl.score = -1 THEN
1
ELSE
NULL

View file

@ -132,7 +132,7 @@ with all_post AS (
NULL
END) AS upvotes,
count(
CASE WHEN pl.score = - 1 THEN
CASE WHEN pl.score = -1 THEN
1
ELSE
NULL
@ -327,7 +327,7 @@ with all_comment AS (
NULL
END) AS upvotes,
count(
CASE WHEN cl.score = - 1 THEN
CASE WHEN cl.score = -1 THEN
1
ELSE
NULL

View file

@ -111,7 +111,7 @@ with all_post AS (
NULL
END) AS upvotes,
count(
CASE WHEN pl.score = - 1 THEN
CASE WHEN pl.score = -1 THEN
1
ELSE
NULL
@ -376,7 +376,7 @@ with all_comment AS (
NULL
END) AS upvotes,
count(
CASE WHEN cl.score = - 1 THEN
CASE WHEN cl.score = -1 THEN
1
ELSE
NULL

View file

@ -74,7 +74,7 @@ SELECT
NULL
END) AS upvotes,
count(
CASE WHEN pl.score = - 1 THEN
CASE WHEN pl.score = -1 THEN
1
ELSE
NULL
@ -430,7 +430,7 @@ SELECT
NULL
END) AS upvotes,
count(
CASE WHEN cl.score = - 1 THEN
CASE WHEN cl.score = -1 THEN
1
ELSE
NULL

View file

@ -94,7 +94,7 @@ SELECT
NULL
END) AS upvotes,
count(
CASE WHEN pl.score = - 1 THEN
CASE WHEN pl.score = -1 THEN
1
ELSE
NULL

View file

@ -95,7 +95,7 @@ SELECT
NULL
END) AS upvotes,
count(
CASE WHEN pl.score = - 1 THEN
CASE WHEN pl.score = -1 THEN
1
ELSE
NULL

View file

@ -61,7 +61,7 @@ SELECT
NULL
END) AS upvotes,
count(
CASE WHEN cl.score = - 1 THEN
CASE WHEN cl.score = -1 THEN
1
ELSE
NULL

View file

@ -72,7 +72,7 @@ SELECT
NULL
END) AS upvotes,
count(
CASE WHEN cl.score = - 1 THEN
CASE WHEN cl.score = -1 THEN
1
ELSE
NULL

View file

@ -82,7 +82,7 @@ SELECT
NULL
END) AS upvotes,
count(
CASE WHEN pl.score = - 1 THEN
CASE WHEN pl.score = -1 THEN
1
ELSE
NULL

View file

@ -83,7 +83,7 @@ SELECT
NULL
END) AS upvotes,
count(
CASE WHEN pl.score = - 1 THEN
CASE WHEN pl.score = -1 THEN
1
ELSE
NULL

View file

@ -96,7 +96,7 @@ SELECT
NULL
END) AS upvotes,
count(
CASE WHEN pl.score = - 1 THEN
CASE WHEN pl.score = -1 THEN
1
ELSE
NULL

View file

@ -97,7 +97,7 @@ SELECT
NULL
END) AS upvotes,
count(
CASE WHEN pl.score = - 1 THEN
CASE WHEN pl.score = -1 THEN
1
ELSE
NULL

View file

@ -351,7 +351,7 @@ SELECT
NULL
END) AS upvotes,
count(
CASE WHEN pl.score = - 1 THEN
CASE WHEN pl.score = -1 THEN
1
ELSE
NULL
@ -566,7 +566,7 @@ SELECT
NULL
END) AS upvotes,
count(
CASE WHEN cl.score = - 1 THEN
CASE WHEN cl.score = -1 THEN
1
ELSE
NULL

View file

@ -494,7 +494,7 @@ SELECT
NULL
END) AS upvotes,
count(
CASE WHEN pl.score = - 1 THEN
CASE WHEN pl.score = -1 THEN
1
ELSE
NULL
@ -741,7 +741,7 @@ SELECT
NULL
END) AS upvotes,
count(
CASE WHEN cl.score = - 1 THEN
CASE WHEN cl.score = -1 THEN
1
ELSE
NULL

View file

@ -405,7 +405,7 @@ SELECT
NULL
END) AS upvotes,
count(
CASE WHEN pl.score = - 1 THEN
CASE WHEN pl.score = -1 THEN
1
ELSE
NULL
@ -662,7 +662,7 @@ SELECT
NULL
END) AS upvotes,
count(
CASE WHEN cl.score = - 1 THEN
CASE WHEN cl.score = -1 THEN
1
ELSE
NULL

View file

@ -198,7 +198,7 @@ FROM
post_id,
sum(score) AS score,
sum(score) FILTER (WHERE score = 1) AS upvotes,
- sum(score) FILTER (WHERE score = - 1) AS downvotes
- sum(score) FILTER (WHERE score = -1) AS downvotes
FROM
post_like
GROUP BY
@ -658,7 +658,7 @@ FROM
NULL
END) AS up,
count(
CASE WHEN l.score = - 1 THEN
CASE WHEN l.score = -1 THEN
1
ELSE
NULL
@ -1062,7 +1062,7 @@ BEGIN
ELSE
upvotes
END,
downvotes = CASE WHEN (OLD.score = - 1) THEN
downvotes = CASE WHEN (OLD.score = -1) THEN
downvotes - 1
ELSE
downvotes
@ -1083,7 +1083,7 @@ BEGIN
ELSE
upvotes
END,
downvotes = CASE WHEN (NEW.score = - 1) THEN
downvotes = CASE WHEN (NEW.score = -1) THEN
downvotes + 1
ELSE
downvotes
@ -1130,7 +1130,7 @@ BEGIN
ELSE
upvotes
END,
downvotes = CASE WHEN (OLD.score = - 1) THEN
downvotes = CASE WHEN (OLD.score = -1) THEN
downvotes - 1
ELSE
downvotes
@ -1151,7 +1151,7 @@ BEGIN
ELSE
upvotes
END,
downvotes = CASE WHEN (NEW.score = - 1) THEN
downvotes = CASE WHEN (NEW.score = -1) THEN
downvotes + 1
ELSE
downvotes

View file

@ -51,7 +51,7 @@ FROM
NULL
END) AS up,
count(
CASE WHEN l.score = - 1 THEN
CASE WHEN l.score = -1 THEN
1
ELSE
NULL
@ -396,7 +396,7 @@ FROM
post_id,
sum(score) AS score,
sum(score) FILTER (WHERE score = 1) AS upvotes,
- sum(score) FILTER (WHERE score = - 1) AS downvotes
- sum(score) FILTER (WHERE score = -1) AS downvotes
FROM
post_like
GROUP BY

View file

@ -52,7 +52,7 @@ FROM
NULL
END) AS up,
count(
CASE WHEN l.score = - 1 THEN
CASE WHEN l.score = -1 THEN
1
ELSE
NULL
@ -398,7 +398,7 @@ FROM
post_id,
sum(score) AS score,
sum(score) FILTER (WHERE score = 1) AS upvotes,
- sum(score) FILTER (WHERE score = - 1) AS downvotes
- sum(score) FILTER (WHERE score = -1) AS downvotes
FROM
post_like
GROUP BY

View file

@ -52,7 +52,7 @@ FROM
NULL
END) AS up,
count(
CASE WHEN l.score = - 1 THEN
CASE WHEN l.score = -1 THEN
1
ELSE
NULL

View file

@ -54,7 +54,7 @@ FROM
NULL
END) AS up,
count(
CASE WHEN l.score = - 1 THEN
CASE WHEN l.score = -1 THEN
1
ELSE
NULL

View file

@ -203,7 +203,7 @@ FROM
post_id,
sum(score) AS score,
sum(score) FILTER (WHERE score = 1) AS upvotes,
- sum(score) FILTER (WHERE score = - 1) AS downvotes
- sum(score) FILTER (WHERE score = -1) AS downvotes
FROM
post_like
GROUP BY
@ -514,7 +514,7 @@ FROM
NULL
END) AS up,
count(
CASE WHEN l.score = - 1 THEN
CASE WHEN l.score = -1 THEN
1
ELSE
NULL

View file

@ -185,7 +185,7 @@ FROM
post_id,
sum(score) AS score,
sum(score) FILTER (WHERE score = 1) AS upvotes,
- sum(score) FILTER (WHERE score = - 1) AS downvotes
- sum(score) FILTER (WHERE score = -1) AS downvotes
FROM
post_like
GROUP BY
@ -524,7 +524,7 @@ FROM
NULL
END) AS up,
count(
CASE WHEN l.score = - 1 THEN
CASE WHEN l.score = -1 THEN
1
ELSE
NULL

View file

@ -1,3 +1,3 @@
ALTER TABLE activity
ADD COLUMN ap_id TEXT;
ADD COLUMN ap_id text;

View file

@ -1,5 +1,5 @@
ALTER TABLE activity
ADD COLUMN user_id INTEGER;
ADD COLUMN user_id integer;
ALTER TABLE activity
DROP COLUMN sensitive;

View file

@ -2,5 +2,5 @@ ALTER TABLE activity
DROP COLUMN user_id;
ALTER TABLE activity
ADD COLUMN sensitive BOOLEAN DEFAULT TRUE;
ADD COLUMN sensitive boolean DEFAULT TRUE;

View file

@ -1,3 +1,3 @@
ALTER TABLE community_follower
ADD COLUMN pending BOOLEAN DEFAULT FALSE;
ADD COLUMN pending boolean DEFAULT FALSE;

View file

@ -1,3 +1,3 @@
ALTER TABLE user_
ADD COLUMN deleted BOOLEAN DEFAULT FALSE NOT NULL;
ADD COLUMN deleted boolean DEFAULT FALSE NOT NULL;

View file

@ -125,7 +125,7 @@ BEGIN
ELSE
upvotes
END,
downvotes = CASE WHEN NEW.score = - 1 THEN
downvotes = CASE WHEN NEW.score = -1 THEN
downvotes + 1
ELSE
downvotes
@ -143,7 +143,7 @@ BEGIN
ELSE
upvotes
END,
downvotes = CASE WHEN OLD.score = - 1 THEN
downvotes = CASE WHEN OLD.score = -1 THEN
downvotes - 1
ELSE
downvotes

View file

@ -78,7 +78,7 @@ BEGIN
ELSE
upvotes
END,
downvotes = CASE WHEN NEW.score = - 1 THEN
downvotes = CASE WHEN NEW.score = -1 THEN
downvotes + 1
ELSE
downvotes
@ -96,7 +96,7 @@ BEGIN
ELSE
upvotes
END,
downvotes = CASE WHEN OLD.score = - 1 THEN
downvotes = CASE WHEN OLD.score = -1 THEN
downvotes - 1
ELSE
downvotes

View file

@ -1,6 +1,6 @@
ALTER TABLE site
ADD COLUMN actor_id varchar(255) NOT NULL UNIQUE DEFAULT generate_unique_changeme (),
ADD COLUMN last_refreshed_at Timestamp NOT NULL DEFAULT now(),
ADD COLUMN last_refreshed_at timestamp NOT NULL DEFAULT now(),
ADD COLUMN inbox_url varchar(255) NOT NULL DEFAULT generate_unique_changeme (),
ADD COLUMN private_key text,
ADD COLUMN public_key text NOT NULL DEFAULT generate_unique_changeme ();

View file

@ -14,7 +14,7 @@ BEGIN
ELSE
upvotes
END,
downvotes = CASE WHEN NEW.score = - 1 THEN
downvotes = CASE WHEN NEW.score = -1 THEN
downvotes + 1
ELSE
downvotes
@ -32,7 +32,7 @@ BEGIN
ELSE
upvotes
END,
downvotes = CASE WHEN OLD.score = - 1 THEN
downvotes = CASE WHEN OLD.score = -1 THEN
downvotes - 1
ELSE
downvotes
@ -63,7 +63,7 @@ BEGIN
ELSE
upvotes
END,
downvotes = CASE WHEN NEW.score = - 1 THEN
downvotes = CASE WHEN NEW.score = -1 THEN
downvotes + 1
ELSE
downvotes
@ -81,7 +81,7 @@ BEGIN
ELSE
upvotes
END,
downvotes = CASE WHEN OLD.score = - 1 THEN
downvotes = CASE WHEN OLD.score = -1 THEN
downvotes - 1
ELSE
downvotes

View file

@ -64,7 +64,7 @@ BEGIN
ELSE
upvotes
END,
downvotes = CASE WHEN NEW.score = - 1 THEN
downvotes = CASE WHEN NEW.score = -1 THEN
downvotes + 1
ELSE
downvotes
@ -73,7 +73,7 @@ BEGIN
1
ELSE
0
END::numeric, pa.downvotes + CASE WHEN NEW.score = - 1 THEN
END::numeric, pa.downvotes + CASE WHEN NEW.score = -1 THEN
1
ELSE
0
@ -91,7 +91,7 @@ BEGIN
ELSE
upvotes
END,
downvotes = CASE WHEN OLD.score = - 1 THEN
downvotes = CASE WHEN OLD.score = -1 THEN
downvotes - 1
ELSE
downvotes
@ -100,7 +100,7 @@ BEGIN
1
ELSE
0
END::numeric, pa.downvotes + CASE WHEN NEW.score = - 1 THEN
END::numeric, pa.downvotes + CASE WHEN NEW.score = -1 THEN
1
ELSE
0
@ -131,7 +131,7 @@ BEGIN
ELSE
upvotes
END,
downvotes = CASE WHEN NEW.score = - 1 THEN
downvotes = CASE WHEN NEW.score = -1 THEN
downvotes + 1
ELSE
downvotes
@ -140,7 +140,7 @@ BEGIN
1
ELSE
0
END::numeric, ca.downvotes + CASE WHEN NEW.score = - 1 THEN
END::numeric, ca.downvotes + CASE WHEN NEW.score = -1 THEN
1
ELSE
0
@ -158,7 +158,7 @@ BEGIN
ELSE
upvotes
END,
downvotes = CASE WHEN OLD.score = - 1 THEN
downvotes = CASE WHEN OLD.score = -1 THEN
downvotes - 1
ELSE
downvotes
@ -167,7 +167,7 @@ BEGIN
1
ELSE
0
END::numeric, ca.downvotes + CASE WHEN NEW.score = - 1 THEN
END::numeric, ca.downvotes + CASE WHEN NEW.score = -1 THEN
1
ELSE
0

View file

@ -33,7 +33,7 @@ BEGIN
ELSE
upvotes
END,
downvotes = CASE WHEN NEW.score = - 1 THEN
downvotes = CASE WHEN NEW.score = -1 THEN
downvotes + 1
ELSE
downvotes
@ -42,7 +42,7 @@ BEGIN
1
ELSE
0
END::numeric, ca.downvotes + CASE WHEN NEW.score = - 1 THEN
END::numeric, ca.downvotes + CASE WHEN NEW.score = -1 THEN
1
ELSE
0
@ -60,7 +60,7 @@ BEGIN
ELSE
upvotes
END,
downvotes = CASE WHEN OLD.score = - 1 THEN
downvotes = CASE WHEN OLD.score = -1 THEN
downvotes - 1
ELSE
downvotes
@ -69,7 +69,7 @@ BEGIN
1
ELSE
0
END::numeric, ca.downvotes + CASE WHEN NEW.score = - 1 THEN
END::numeric, ca.downvotes + CASE WHEN NEW.score = -1 THEN
1
ELSE
0
@ -551,7 +551,7 @@ BEGIN
ELSE
upvotes
END,
downvotes = CASE WHEN NEW.score = - 1 THEN
downvotes = CASE WHEN NEW.score = -1 THEN
downvotes + 1
ELSE
downvotes
@ -560,7 +560,7 @@ BEGIN
1
ELSE
0
END::numeric, pa.downvotes + CASE WHEN NEW.score = - 1 THEN
END::numeric, pa.downvotes + CASE WHEN NEW.score = -1 THEN
1
ELSE
0
@ -578,7 +578,7 @@ BEGIN
ELSE
upvotes
END,
downvotes = CASE WHEN OLD.score = - 1 THEN
downvotes = CASE WHEN OLD.score = -1 THEN
downvotes - 1
ELSE
downvotes
@ -587,7 +587,7 @@ BEGIN
1
ELSE
0
END::numeric, pa.downvotes + CASE WHEN NEW.score = - 1 THEN
END::numeric, pa.downvotes + CASE WHEN NEW.score = -1 THEN
1
ELSE
0

View file

@ -33,8 +33,10 @@ CREATE TABLE person_saved_combined (
id serial PRIMARY KEY,
saved timestamptz NOT NULL,
person_id int NOT NULL REFERENCES person ON UPDATE CASCADE ON DELETE CASCADE,
post_id int UNIQUE REFERENCES post ON UPDATE CASCADE ON DELETE CASCADE,
comment_id int UNIQUE REFERENCES COMMENT ON UPDATE CASCADE ON DELETE CASCADE,
post_id int REFERENCES post ON UPDATE CASCADE ON DELETE CASCADE,
comment_id int REFERENCES COMMENT ON UPDATE CASCADE ON DELETE CASCADE,
UNIQUE (person_id, post_id),
UNIQUE (person_id, comment_id),
-- Make sure only one of the columns is not null
CHECK (num_nonnulls (post_id, comment_id) = 1)
);

View file

@ -62,8 +62,8 @@ ALTER TABLE local_site
-- Add the new column to both tables (null means no limit)
ALTER TABLE local_user
ADD COLUMN default_post_time_range_seconds INTEGER;
ADD COLUMN default_post_time_range_seconds integer;
ALTER TABLE local_site
ADD COLUMN default_post_time_range_seconds INTEGER;
ADD COLUMN default_post_time_range_seconds integer;

View file

@ -0,0 +1,2 @@
DROP TABLE person_liked_combined;

View file

@ -0,0 +1,49 @@
-- Creates combined tables for
-- person_liked: (comment, post)
--
-- This one is special, because you use the liked date, not the ordinary published
CREATE TABLE person_liked_combined (
id serial PRIMARY KEY,
liked timestamptz NOT NULL,
like_score smallint NOT NULL,
person_id int NOT NULL REFERENCES person ON UPDATE CASCADE ON DELETE CASCADE,
post_id int REFERENCES post ON UPDATE CASCADE ON DELETE CASCADE,
comment_id int REFERENCES COMMENT ON UPDATE CASCADE ON DELETE CASCADE,
UNIQUE (person_id, post_id),
UNIQUE (person_id, comment_id),
-- Make sure only one of the columns is not null
CHECK (num_nonnulls (post_id, comment_id) = 1)
);
CREATE INDEX idx_person_liked_combined_published ON person_liked_combined (liked DESC, id DESC);
CREATE INDEX idx_person_liked_combined ON person_liked_combined (person_id);
-- Updating the history
INSERT INTO person_liked_combined (liked, like_score, person_id, post_id, comment_id)
SELECT
pa.liked,
pa.like_score,
pa.person_id,
pa.post_id,
NULL::int
FROM
post_actions pa
INNER JOIN person p ON pa.person_id = p.id
WHERE
pa.liked IS NOT NULL
AND p.local = TRUE
UNION ALL
SELECT
ca.liked,
ca.like_score,
ca.person_id,
NULL::int,
ca.comment_id
FROM
comment_actions ca
INNER JOIN person p ON ca.person_id = p.id
WHERE
liked IS NOT NULL
AND p.local = TRUE;

View file

@ -0,0 +1,10 @@
#!/usr/bin/env bash
set -e
version=5.6
wget https://github.com/darold/pgFormatter/archive/refs/tags/v${version}.tar.gz
tar xzf v${version}.tar.gz
cd pgFormatter-${version}/
perl Makefile.PL
make && make install
cd ../ && rm -rf v${version}.tar.gz && rm -rf pgFormatter-${version} #clean up

View file

@ -30,6 +30,7 @@ use lemmy_api::{
generate_totp_secret::generate_totp_secret,
get_captcha::get_captcha,
list_hidden::list_person_hidden,
list_liked::list_person_liked,
list_logins::list_logins,
list_media::list_media,
list_read::list_person_read,
@ -371,6 +372,7 @@ pub fn config(cfg: &mut ServiceConfig, rate_limit: &RateLimitCell) {
.route("/saved", get().to(list_person_saved))
.route("/read", get().to(list_person_read))
.route("/hidden", get().to(list_person_hidden))
.route("/liked", get().to(list_person_liked))
.route("/settings/save", put().to(save_user_settings))
// Account settings import / export have a strict rate limit
.service(