mirror of
https://github.com/LemmyNet/lemmy.git
synced 2024-11-28 20:31:24 +00:00
Merge remote-tracking branch 'origin/main' into auto_mark_posts_as_read
This commit is contained in:
commit
bebb1673f2
47 changed files with 2567 additions and 1563 deletions
17
Cargo.lock
generated
17
Cargo.lock
generated
|
@ -1281,6 +1281,15 @@ dependencies = [
|
|||
"tokio-postgres",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "diesel-bind-if-some"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ed8ce9db476124d2eaf4c9db45dc6581b8e8c4c4d47d5e0f39de1fb55dfb2a7"
|
||||
dependencies = [
|
||||
"diesel",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "diesel-derive-enum"
|
||||
version = "2.1.0"
|
||||
|
@ -2613,6 +2622,7 @@ dependencies = [
|
|||
"derive-new",
|
||||
"diesel",
|
||||
"diesel-async",
|
||||
"diesel-bind-if-some",
|
||||
"diesel-derive-enum",
|
||||
"diesel-derive-newtype",
|
||||
"diesel_ltree",
|
||||
|
@ -2634,6 +2644,7 @@ dependencies = [
|
|||
"tokio-postgres-rustls",
|
||||
"tracing",
|
||||
"ts-rs",
|
||||
"tuplex",
|
||||
"url",
|
||||
"uuid",
|
||||
]
|
||||
|
@ -5272,6 +5283,12 @@ dependencies = [
|
|||
"termcolor",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tuplex"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "676ac81d5454c4dcf37955d34fa8626ede3490f744b86ca14a7b90168d2a08aa"
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.17.0"
|
||||
|
|
|
@ -160,6 +160,8 @@ i-love-jesus = { version = "0.1.0" }
|
|||
clap = { version = "4.5.13", features = ["derive", "env"] }
|
||||
pretty_assertions = "1.4.0"
|
||||
derive-new = "0.7.0"
|
||||
diesel-bind-if-some = "0.1.0"
|
||||
tuplex = "0.1.2"
|
||||
|
||||
[dependencies]
|
||||
lemmy_api = { workspace = true }
|
||||
|
|
|
@ -153,7 +153,6 @@ pub async fn update_read_comments(
|
|||
person_id,
|
||||
post_id,
|
||||
read_comments,
|
||||
..PersonPostAggregatesForm::default()
|
||||
};
|
||||
|
||||
PersonPostAggregates::upsert(pool, &person_post_agg_form).await?;
|
||||
|
|
|
@ -194,10 +194,16 @@ impl Object for ApubCommunity {
|
|||
LanguageTag::to_language_id_multiple(group.language, &mut context.pool()).await?;
|
||||
|
||||
let timestamp = group.updated.or(group.published).unwrap_or_else(naive_now);
|
||||
let community = Community::insert_apub(&mut context.pool(), timestamp, &form).await?;
|
||||
let community: ApubCommunity = Community::insert_apub(&mut context.pool(), timestamp, &form)
|
||||
.await?
|
||||
.into();
|
||||
CommunityLanguage::update(&mut context.pool(), languages, community.id).await?;
|
||||
|
||||
let community: ApubCommunity = community.into();
|
||||
// Need to fetch mods synchronously, otherwise fetching a post in community with
|
||||
// `posting_restricted_to_mods` can fail if mods havent been fetched yet.
|
||||
if let Some(moderators) = group.attributed_to {
|
||||
moderators.dereference(&community, context).await.ok();
|
||||
}
|
||||
|
||||
// These collections are not necessary for Lemmy to work, so ignore errors.
|
||||
let community_ = community.clone();
|
||||
|
@ -210,9 +216,6 @@ impl Object for ApubCommunity {
|
|||
if let Some(featured) = group.featured {
|
||||
featured.dereference(&community_, &context_).await.ok();
|
||||
}
|
||||
if let Some(moderators) = group.attributed_to {
|
||||
moderators.dereference(&community_, &context_).await.ok();
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
|
||||
|
|
|
@ -37,6 +37,8 @@ full = [
|
|||
"tokio-postgres-rustls",
|
||||
"rustls",
|
||||
"i-love-jesus",
|
||||
"tuplex",
|
||||
"diesel-bind-if-some",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
|
@ -76,8 +78,10 @@ rustls = { workspace = true, optional = true }
|
|||
uuid = { workspace = true, features = ["v4"] }
|
||||
i-love-jesus = { workspace = true, optional = true }
|
||||
anyhow = { workspace = true }
|
||||
diesel-bind-if-some = { workspace = true, optional = true }
|
||||
moka.workspace = true
|
||||
derive-new.workspace = true
|
||||
tuplex = { workspace = true, optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
serial_test = { workspace = true }
|
||||
|
|
|
@ -38,7 +38,7 @@ AS $a$
|
|||
BEGIN
|
||||
EXECUTE replace($b$
|
||||
-- When a thing gets a vote, update its aggregates and its creator's aggregates
|
||||
CALL r.create_triggers ('thing_like', $$
|
||||
CALL r.create_triggers ('thing_actions', $$
|
||||
BEGIN
|
||||
WITH thing_diff AS ( UPDATE
|
||||
thing_aggregates AS a
|
||||
|
@ -46,7 +46,8 @@ BEGIN
|
|||
score = a.score + diff.upvotes - diff.downvotes, upvotes = a.upvotes + diff.upvotes, downvotes = a.downvotes + diff.downvotes, controversy_rank = r.controversy_rank ((a.upvotes + diff.upvotes)::numeric, (a.downvotes + diff.downvotes)::numeric)
|
||||
FROM (
|
||||
SELECT
|
||||
(thing_like).thing_id, coalesce(sum(count_diff) FILTER (WHERE (thing_like).score = 1), 0) AS upvotes, coalesce(sum(count_diff) FILTER (WHERE (thing_like).score != 1), 0) AS downvotes FROM select_old_and_new_rows AS old_and_new_rows GROUP BY (thing_like).thing_id) AS diff
|
||||
(thing_actions).thing_id, coalesce(sum(count_diff) FILTER (WHERE (thing_actions).like_score = 1), 0) AS upvotes, coalesce(sum(count_diff) FILTER (WHERE (thing_actions).like_score != 1), 0) AS downvotes FROM select_old_and_new_rows AS old_and_new_rows
|
||||
WHERE (thing_actions).like_score IS NOT NULL GROUP BY (thing_actions).thing_id) AS diff
|
||||
WHERE
|
||||
a.thing_id = diff.thing_id
|
||||
AND (diff.upvotes, diff.downvotes) != (0, 0)
|
||||
|
@ -360,7 +361,7 @@ CREATE TRIGGER comment_count
|
|||
-- 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.
|
||||
CALL r.create_triggers ('community_follower', $$
|
||||
CALL r.create_triggers ('community_actions', $$
|
||||
BEGIN
|
||||
UPDATE
|
||||
community_aggregates AS a
|
||||
|
@ -368,10 +369,11 @@ BEGIN
|
|||
subscribers = a.subscribers + diff.subscribers, subscribers_local = a.subscribers_local + diff.subscribers_local
|
||||
FROM (
|
||||
SELECT
|
||||
(community_follower).community_id, coalesce(sum(count_diff) FILTER (WHERE community.local), 0) AS subscribers, coalesce(sum(count_diff) FILTER (WHERE person.local), 0) AS subscribers_local
|
||||
(community_actions).community_id, coalesce(sum(count_diff) FILTER (WHERE community.local), 0) AS subscribers, coalesce(sum(count_diff) FILTER (WHERE person.local), 0) AS subscribers_local
|
||||
FROM select_old_and_new_rows AS old_and_new_rows
|
||||
LEFT JOIN community ON community.id = (community_follower).community_id
|
||||
LEFT JOIN person ON person.id = (community_follower).person_id GROUP BY (community_follower).community_id) AS diff
|
||||
LEFT JOIN community ON community.id = (community_actions).community_id
|
||||
LEFT JOIN person ON person.id = (community_actions).person_id
|
||||
WHERE (community_actions).followed IS NOT NULL GROUP BY (community_actions).community_id) AS diff
|
||||
WHERE
|
||||
a.community_id = diff.community_id
|
||||
AND (diff.subscribers, diff.subscribers_local) != (0, 0);
|
||||
|
@ -541,7 +543,7 @@ CREATE FUNCTION r.delete_follow_before_person ()
|
|||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
DELETE FROM community_follower AS c
|
||||
DELETE FROM community_actions AS c
|
||||
WHERE c.person_id = OLD.id;
|
||||
RETURN OLD;
|
||||
END;
|
||||
|
|
|
@ -151,3 +151,118 @@ DECLARE
|
|||
END;
|
||||
$a$;
|
||||
|
||||
-- Edit community aggregates to include voters as active users
|
||||
CREATE OR REPLACE FUNCTION r.community_aggregates_activity (i text)
|
||||
RETURNS TABLE (
|
||||
count_ bigint,
|
||||
community_id_ integer)
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
RETURN query
|
||||
SELECT
|
||||
count(*),
|
||||
community_id
|
||||
FROM (
|
||||
SELECT
|
||||
c.creator_id,
|
||||
p.community_id
|
||||
FROM
|
||||
comment c
|
||||
INNER JOIN post p ON c.post_id = p.id
|
||||
INNER JOIN person pe ON c.creator_id = pe.id
|
||||
WHERE
|
||||
c.published > ('now'::timestamp - i::interval)
|
||||
AND pe.bot_account = FALSE
|
||||
UNION
|
||||
SELECT
|
||||
p.creator_id,
|
||||
p.community_id
|
||||
FROM
|
||||
post p
|
||||
INNER JOIN person pe ON p.creator_id = pe.id
|
||||
WHERE
|
||||
p.published > ('now'::timestamp - i::interval)
|
||||
AND pe.bot_account = FALSE
|
||||
UNION
|
||||
SELECT
|
||||
pl.person_id,
|
||||
p.community_id
|
||||
FROM
|
||||
post_like pl
|
||||
INNER JOIN post p ON pl.post_id = p.id
|
||||
INNER JOIN person pe ON pl.person_id = pe.id
|
||||
WHERE
|
||||
pl.published > ('now'::timestamp - i::interval)
|
||||
AND pe.bot_account = FALSE
|
||||
UNION
|
||||
SELECT
|
||||
cl.person_id,
|
||||
p.community_id
|
||||
FROM
|
||||
comment_like cl
|
||||
INNER JOIN comment c ON cl.comment_id = c.id
|
||||
INNER JOIN post p ON c.post_id = p.id
|
||||
INNER JOIN person pe ON cl.person_id = pe.id
|
||||
WHERE
|
||||
cl.published > ('now'::timestamp - i::interval)
|
||||
AND pe.bot_account = FALSE) a
|
||||
GROUP BY
|
||||
community_id;
|
||||
END;
|
||||
$$;
|
||||
|
||||
-- Edit site aggregates to include voters and people who have read posts as active users
|
||||
CREATE OR REPLACE FUNCTION r.site_aggregates_activity (i text)
|
||||
RETURNS integer
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
count_ integer;
|
||||
BEGIN
|
||||
SELECT
|
||||
count(*) INTO count_
|
||||
FROM (
|
||||
SELECT
|
||||
c.creator_id
|
||||
FROM
|
||||
comment c
|
||||
INNER JOIN person pe ON c.creator_id = pe.id
|
||||
WHERE
|
||||
c.published > ('now'::timestamp - i::interval)
|
||||
AND pe.local = TRUE
|
||||
AND pe.bot_account = FALSE
|
||||
UNION
|
||||
SELECT
|
||||
p.creator_id
|
||||
FROM
|
||||
post p
|
||||
INNER JOIN person pe ON p.creator_id = pe.id
|
||||
WHERE
|
||||
p.published > ('now'::timestamp - i::interval)
|
||||
AND pe.local = TRUE
|
||||
AND pe.bot_account = FALSE
|
||||
UNION
|
||||
SELECT
|
||||
pl.person_id
|
||||
FROM
|
||||
post_like pl
|
||||
INNER JOIN person pe ON pl.person_id = pe.id
|
||||
WHERE
|
||||
pl.published > ('now'::timestamp - i::interval)
|
||||
AND pe.local = TRUE
|
||||
AND pe.bot_account = FALSE
|
||||
UNION
|
||||
SELECT
|
||||
cl.person_id
|
||||
FROM
|
||||
comment_like cl
|
||||
INNER JOIN person pe ON cl.person_id = pe.id
|
||||
WHERE
|
||||
cl.published > ('now'::timestamp - i::interval)
|
||||
AND pe.local = TRUE
|
||||
AND pe.bot_account = FALSE) a;
|
||||
RETURN count_;
|
||||
END;
|
||||
$$;
|
||||
|
||||
|
|
|
@ -2,10 +2,17 @@ use crate::{
|
|||
aggregates::structs::{PersonPostAggregates, PersonPostAggregatesForm},
|
||||
diesel::OptionalExtension,
|
||||
newtypes::{PersonId, PostId},
|
||||
schema::person_post_aggregates::dsl::{person_id, person_post_aggregates, post_id},
|
||||
utils::{get_conn, DbPool},
|
||||
schema::post_actions,
|
||||
utils::{find_action, get_conn, now, DbPool},
|
||||
};
|
||||
use diesel::{
|
||||
expression::SelectableHelper,
|
||||
insert_into,
|
||||
result::Error,
|
||||
ExpressionMethods,
|
||||
NullableExpressionMethods,
|
||||
QueryDsl,
|
||||
};
|
||||
use diesel::{insert_into, result::Error, QueryDsl};
|
||||
use diesel_async::RunQueryDsl;
|
||||
|
||||
impl PersonPostAggregates {
|
||||
|
@ -14,11 +21,13 @@ impl PersonPostAggregates {
|
|||
form: &PersonPostAggregatesForm,
|
||||
) -> Result<Self, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
insert_into(person_post_aggregates)
|
||||
let form = (form, post_actions::read_comments.eq(now().nullable()));
|
||||
insert_into(post_actions::table)
|
||||
.values(form)
|
||||
.on_conflict((person_id, post_id))
|
||||
.on_conflict((post_actions::person_id, post_actions::post_id))
|
||||
.do_update()
|
||||
.set(form)
|
||||
.returning(Self::as_select())
|
||||
.get_result::<Self>(conn)
|
||||
.await
|
||||
}
|
||||
|
@ -28,8 +37,8 @@ impl PersonPostAggregates {
|
|||
post_id_: PostId,
|
||||
) -> Result<Option<Self>, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
person_post_aggregates
|
||||
.find((person_id_, post_id_))
|
||||
find_action(post_actions::read_comments, (person_id_, post_id_))
|
||||
.select(Self::as_select())
|
||||
.first(conn)
|
||||
.await
|
||||
.optional()
|
||||
|
|
|
@ -4,12 +4,14 @@ use crate::schema::{
|
|||
comment_aggregates,
|
||||
community_aggregates,
|
||||
person_aggregates,
|
||||
person_post_aggregates,
|
||||
post_actions,
|
||||
post_aggregates,
|
||||
site_aggregates,
|
||||
};
|
||||
use chrono::{DateTime, Utc};
|
||||
#[cfg(feature = "full")]
|
||||
use diesel::{dsl, expression_methods::NullableExpressionMethods};
|
||||
#[cfg(feature = "full")]
|
||||
use i_love_jesus::CursorKeysModule;
|
||||
use serde::{Deserialize, Serialize};
|
||||
#[cfg(feature = "full")]
|
||||
|
@ -151,7 +153,7 @@ pub struct PostAggregates {
|
|||
feature = "full",
|
||||
derive(Queryable, Selectable, Associations, Identifiable)
|
||||
)]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = person_post_aggregates))]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = post_actions))]
|
||||
#[cfg_attr(feature = "full", diesel(primary_key(person_id, post_id)))]
|
||||
#[cfg_attr(feature = "full", diesel(belongs_to(crate::source::person::Person)))]
|
||||
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
|
||||
|
@ -162,18 +164,22 @@ pub struct PersonPostAggregates {
|
|||
/// The number of comments they've read on that post.
|
||||
///
|
||||
/// This is updated to the current post comment count every time they view a post.
|
||||
#[cfg_attr(feature = "full", diesel(select_expression = post_actions::read_comments_amount.assume_not_null()))]
|
||||
#[cfg_attr(feature = "full", diesel(select_expression_type = dsl::AssumeNotNull<post_actions::read_comments_amount>))]
|
||||
pub read_comments: i64,
|
||||
#[cfg_attr(feature = "full", diesel(select_expression = post_actions::read_comments.assume_not_null()))]
|
||||
#[cfg_attr(feature = "full", diesel(select_expression_type = dsl::AssumeNotNull<post_actions::read_comments>))]
|
||||
pub published: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = person_post_aggregates))]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = post_actions))]
|
||||
pub struct PersonPostAggregatesForm {
|
||||
pub person_id: PersonId,
|
||||
pub post_id: PostId,
|
||||
#[cfg_attr(feature = "full", diesel(column_name = read_comments_amount))]
|
||||
pub read_comments: i64,
|
||||
pub published: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Clone, Copy, Hash)]
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::{
|
||||
diesel::{DecoratableTarget, OptionalExtension},
|
||||
newtypes::{CommentId, DbUrl, PersonId},
|
||||
schema::comment,
|
||||
schema::{comment, comment_actions},
|
||||
source::comment::{
|
||||
Comment,
|
||||
CommentInsertForm,
|
||||
|
@ -12,10 +12,25 @@ use crate::{
|
|||
CommentUpdateForm,
|
||||
},
|
||||
traits::{Crud, Likeable, Saveable},
|
||||
utils::{functions::coalesce, get_conn, naive_now, DbPool, DELETED_REPLACEMENT_TEXT},
|
||||
utils::{
|
||||
functions::coalesce,
|
||||
get_conn,
|
||||
naive_now,
|
||||
now,
|
||||
uplete,
|
||||
DbPool,
|
||||
DELETED_REPLACEMENT_TEXT,
|
||||
},
|
||||
};
|
||||
use chrono::{DateTime, Utc};
|
||||
use diesel::{dsl::insert_into, result::Error, ExpressionMethods, QueryDsl};
|
||||
use diesel::{
|
||||
dsl::insert_into,
|
||||
expression::SelectableHelper,
|
||||
result::Error,
|
||||
ExpressionMethods,
|
||||
NullableExpressionMethods,
|
||||
QueryDsl,
|
||||
};
|
||||
use diesel_async::RunQueryDsl;
|
||||
use diesel_ltree::Ltree;
|
||||
use url::Url;
|
||||
|
@ -141,13 +156,17 @@ impl Likeable for CommentLike {
|
|||
type Form = CommentLikeForm;
|
||||
type IdType = CommentId;
|
||||
async fn like(pool: &mut DbPool<'_>, comment_like_form: &CommentLikeForm) -> Result<Self, Error> {
|
||||
use crate::schema::comment_like::dsl::{comment_id, comment_like, person_id};
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
insert_into(comment_like)
|
||||
let comment_like_form = (
|
||||
comment_like_form,
|
||||
comment_actions::liked.eq(now().nullable()),
|
||||
);
|
||||
insert_into(comment_actions::table)
|
||||
.values(comment_like_form)
|
||||
.on_conflict((comment_id, person_id))
|
||||
.on_conflict((comment_actions::comment_id, comment_actions::person_id))
|
||||
.do_update()
|
||||
.set(comment_like_form)
|
||||
.returning(Self::as_select())
|
||||
.get_result::<Self>(conn)
|
||||
.await
|
||||
}
|
||||
|
@ -155,11 +174,12 @@ impl Likeable for CommentLike {
|
|||
pool: &mut DbPool<'_>,
|
||||
person_id: PersonId,
|
||||
comment_id: CommentId,
|
||||
) -> Result<usize, Error> {
|
||||
use crate::schema::comment_like::dsl::comment_like;
|
||||
) -> Result<uplete::Count, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
diesel::delete(comment_like.find((person_id, comment_id)))
|
||||
.execute(conn)
|
||||
uplete::new(comment_actions::table.find((person_id, comment_id)))
|
||||
.set_null(comment_actions::like_score)
|
||||
.set_null(comment_actions::liked)
|
||||
.get_result(conn)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
@ -171,26 +191,30 @@ impl Saveable for CommentSaved {
|
|||
pool: &mut DbPool<'_>,
|
||||
comment_saved_form: &CommentSavedForm,
|
||||
) -> Result<Self, Error> {
|
||||
use crate::schema::comment_saved::dsl::{comment_id, comment_saved, person_id};
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
insert_into(comment_saved)
|
||||
let comment_saved_form = (
|
||||
comment_saved_form,
|
||||
comment_actions::saved.eq(now().nullable()),
|
||||
);
|
||||
insert_into(comment_actions::table)
|
||||
.values(comment_saved_form)
|
||||
.on_conflict((comment_id, person_id))
|
||||
.on_conflict((comment_actions::comment_id, comment_actions::person_id))
|
||||
.do_update()
|
||||
.set(comment_saved_form)
|
||||
.returning(Self::as_select())
|
||||
.get_result::<Self>(conn)
|
||||
.await
|
||||
}
|
||||
async fn unsave(
|
||||
pool: &mut DbPool<'_>,
|
||||
comment_saved_form: &CommentSavedForm,
|
||||
) -> Result<usize, Error> {
|
||||
use crate::schema::comment_saved::dsl::comment_saved;
|
||||
) -> Result<uplete::Count, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
diesel::delete(
|
||||
comment_saved.find((comment_saved_form.person_id, comment_saved_form.comment_id)),
|
||||
uplete::new(
|
||||
comment_actions::table.find((comment_saved_form.person_id, comment_saved_form.comment_id)),
|
||||
)
|
||||
.execute(conn)
|
||||
.set_null(comment_actions::saved)
|
||||
.get_result(conn)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
@ -216,7 +240,7 @@ mod tests {
|
|||
post::{Post, PostInsertForm},
|
||||
},
|
||||
traits::{Crud, Likeable, Saveable},
|
||||
utils::build_db_pool_for_tests,
|
||||
utils::{build_db_pool_for_tests, uplete},
|
||||
};
|
||||
use diesel_ltree::Ltree;
|
||||
use lemmy_utils::error::LemmyResult;
|
||||
|
@ -342,8 +366,8 @@ mod tests {
|
|||
format!("0.{}.{}", expected_comment.id, inserted_child_comment.id),
|
||||
inserted_child_comment.path.0,
|
||||
);
|
||||
assert_eq!(1, like_removed);
|
||||
assert_eq!(1, saved_removed);
|
||||
assert_eq!(uplete::Count::only_updated(1), like_removed);
|
||||
assert_eq!(uplete::Count::only_deleted(1), saved_removed);
|
||||
assert_eq!(1, num_deleted);
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -1,14 +1,7 @@
|
|||
use crate::{
|
||||
diesel::{DecoratableTarget, OptionalExtension},
|
||||
newtypes::{CommunityId, DbUrl, PersonId},
|
||||
schema::{
|
||||
community,
|
||||
community_follower,
|
||||
community_moderator,
|
||||
community_person_ban,
|
||||
instance,
|
||||
post,
|
||||
},
|
||||
schema::{community, community_actions, instance, post},
|
||||
source::{
|
||||
actor_language::CommunityLanguage,
|
||||
community::{
|
||||
|
@ -27,8 +20,12 @@ use crate::{
|
|||
},
|
||||
traits::{ApubActor, Bannable, Crud, Followable, Joinable},
|
||||
utils::{
|
||||
action_query,
|
||||
find_action,
|
||||
functions::{coalesce, lower},
|
||||
get_conn,
|
||||
now,
|
||||
uplete,
|
||||
DbPool,
|
||||
},
|
||||
ListingType,
|
||||
|
@ -38,6 +35,7 @@ use chrono::{DateTime, Utc};
|
|||
use diesel::{
|
||||
deserialize,
|
||||
dsl::{self, exists, insert_into, not},
|
||||
expression::SelectableHelper,
|
||||
pg::Pg,
|
||||
result::Error,
|
||||
select,
|
||||
|
@ -93,8 +91,19 @@ impl Joinable for CommunityModerator {
|
|||
community_moderator_form: &CommunityModeratorForm,
|
||||
) -> Result<Self, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
insert_into(community_moderator::table)
|
||||
let community_moderator_form = (
|
||||
community_moderator_form,
|
||||
community_actions::became_moderator.eq(now().nullable()),
|
||||
);
|
||||
insert_into(community_actions::table)
|
||||
.values(community_moderator_form)
|
||||
.on_conflict((
|
||||
community_actions::person_id,
|
||||
community_actions::community_id,
|
||||
))
|
||||
.do_update()
|
||||
.set(community_moderator_form)
|
||||
.returning(Self::as_select())
|
||||
.get_result::<Self>(conn)
|
||||
.await
|
||||
}
|
||||
|
@ -102,13 +111,14 @@ impl Joinable for CommunityModerator {
|
|||
async fn leave(
|
||||
pool: &mut DbPool<'_>,
|
||||
community_moderator_form: &CommunityModeratorForm,
|
||||
) -> Result<usize, Error> {
|
||||
) -> Result<uplete::Count, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
diesel::delete(community_moderator::table.find((
|
||||
uplete::new(community_actions::table.find((
|
||||
community_moderator_form.person_id,
|
||||
community_moderator_form.community_id,
|
||||
)))
|
||||
.execute(conn)
|
||||
.set_null(community_actions::became_moderator)
|
||||
.get_result(conn)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
@ -225,26 +235,26 @@ impl CommunityModerator {
|
|||
pub async fn delete_for_community(
|
||||
pool: &mut DbPool<'_>,
|
||||
for_community_id: CommunityId,
|
||||
) -> Result<usize, Error> {
|
||||
) -> Result<uplete::Count, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
|
||||
diesel::delete(
|
||||
community_moderator::table.filter(community_moderator::community_id.eq(for_community_id)),
|
||||
uplete::new(
|
||||
community_actions::table.filter(community_actions::community_id.eq(for_community_id)),
|
||||
)
|
||||
.execute(conn)
|
||||
.set_null(community_actions::became_moderator)
|
||||
.get_result(conn)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn leave_all_communities(
|
||||
pool: &mut DbPool<'_>,
|
||||
for_person_id: PersonId,
|
||||
) -> Result<usize, Error> {
|
||||
) -> Result<uplete::Count, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
diesel::delete(
|
||||
community_moderator::table.filter(community_moderator::person_id.eq(for_person_id)),
|
||||
)
|
||||
.execute(conn)
|
||||
.await
|
||||
uplete::new(community_actions::table.filter(community_actions::person_id.eq(for_person_id)))
|
||||
.set_null(community_actions::became_moderator)
|
||||
.get_result(conn)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_person_moderated_communities(
|
||||
|
@ -252,9 +262,9 @@ impl CommunityModerator {
|
|||
for_person_id: PersonId,
|
||||
) -> Result<Vec<CommunityId>, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
community_moderator::table
|
||||
.filter(community_moderator::person_id.eq(for_person_id))
|
||||
.select(community_moderator::community_id)
|
||||
action_query(community_actions::became_moderator)
|
||||
.filter(community_actions::person_id.eq(for_person_id))
|
||||
.select(community_actions::community_id)
|
||||
.load::<CommunityId>(conn)
|
||||
.await
|
||||
}
|
||||
|
@ -273,16 +283,17 @@ impl CommunityModerator {
|
|||
persons.push(mod_person_id);
|
||||
persons.dedup();
|
||||
|
||||
let res = community_moderator::table
|
||||
.filter(community_moderator::community_id.eq(for_community_id))
|
||||
.filter(community_moderator::person_id.eq_any(persons))
|
||||
.order_by(community_moderator::published)
|
||||
let res = action_query(community_actions::became_moderator)
|
||||
.filter(community_actions::community_id.eq(for_community_id))
|
||||
.filter(community_actions::person_id.eq_any(persons))
|
||||
.order_by(community_actions::became_moderator)
|
||||
.select(community_actions::person_id)
|
||||
// This does a limit 1 select first
|
||||
.first::<CommunityModerator>(conn)
|
||||
.first::<PersonId>(conn)
|
||||
.await?;
|
||||
|
||||
// If the first result sorted by published is the acting mod
|
||||
if res.person_id == mod_person_id {
|
||||
if res == mod_person_id {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(LemmyErrorType::NotHigherMod)?
|
||||
|
@ -298,14 +309,19 @@ impl Bannable for CommunityPersonBan {
|
|||
community_person_ban_form: &CommunityPersonBanForm,
|
||||
) -> Result<Self, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
insert_into(community_person_ban::table)
|
||||
let community_person_ban_form = (
|
||||
community_person_ban_form,
|
||||
community_actions::received_ban.eq(now().nullable()),
|
||||
);
|
||||
insert_into(community_actions::table)
|
||||
.values(community_person_ban_form)
|
||||
.on_conflict((
|
||||
community_person_ban::community_id,
|
||||
community_person_ban::person_id,
|
||||
community_actions::community_id,
|
||||
community_actions::person_id,
|
||||
))
|
||||
.do_update()
|
||||
.set(community_person_ban_form)
|
||||
.returning(Self::as_select())
|
||||
.get_result::<Self>(conn)
|
||||
.await
|
||||
}
|
||||
|
@ -313,20 +329,22 @@ impl Bannable for CommunityPersonBan {
|
|||
async fn unban(
|
||||
pool: &mut DbPool<'_>,
|
||||
community_person_ban_form: &CommunityPersonBanForm,
|
||||
) -> Result<usize, Error> {
|
||||
) -> Result<uplete::Count, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
diesel::delete(community_person_ban::table.find((
|
||||
uplete::new(community_actions::table.find((
|
||||
community_person_ban_form.person_id,
|
||||
community_person_ban_form.community_id,
|
||||
)))
|
||||
.execute(conn)
|
||||
.set_null(community_actions::received_ban)
|
||||
.set_null(community_actions::ban_expires)
|
||||
.get_result(conn)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
impl CommunityFollower {
|
||||
pub fn select_subscribed_type() -> dsl::Nullable<community_follower::state> {
|
||||
community_follower::state.nullable()
|
||||
pub fn select_subscribed_type() -> dsl::Nullable<community_actions::follow_state> {
|
||||
community_actions::follow_state.nullable()
|
||||
}
|
||||
|
||||
/// Check if a remote instance has any followers on local instance. For this it is enough to check
|
||||
|
@ -336,9 +354,10 @@ impl CommunityFollower {
|
|||
remote_community_id: CommunityId,
|
||||
) -> LemmyResult<()> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
select(exists(community_follower::table.filter(
|
||||
community_follower::community_id.eq(remote_community_id),
|
||||
)))
|
||||
select(exists(
|
||||
action_query(community_actions::followed)
|
||||
.filter(community_actions::community_id.eq(remote_community_id)),
|
||||
))
|
||||
.get_result::<bool>(conn)
|
||||
.await?
|
||||
.then_some(())
|
||||
|
@ -352,13 +371,16 @@ impl CommunityFollower {
|
|||
approver_id: PersonId,
|
||||
) -> LemmyResult<()> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
diesel::update(community_follower::table.find((follower_id, community_id)))
|
||||
.set((
|
||||
community_follower::state.eq(CommunityFollowerState::Accepted),
|
||||
community_follower::approver_id.eq(approver_id),
|
||||
))
|
||||
.get_result::<Self>(conn)
|
||||
.await?;
|
||||
diesel::update(find_action(
|
||||
community_actions::followed,
|
||||
(follower_id, community_id),
|
||||
))
|
||||
.set((
|
||||
community_actions::follow_state.eq(CommunityFollowerState::Accepted),
|
||||
community_actions::follow_approver_id.eq(approver_id),
|
||||
))
|
||||
.execute(conn)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -382,14 +404,16 @@ impl Followable for CommunityFollower {
|
|||
type Form = CommunityFollowerForm;
|
||||
async fn follow(pool: &mut DbPool<'_>, form: &CommunityFollowerForm) -> Result<Self, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
insert_into(community_follower::table)
|
||||
let form = (form, community_actions::followed.eq(now().nullable()));
|
||||
insert_into(community_actions::table)
|
||||
.values(form)
|
||||
.on_conflict((
|
||||
community_follower::community_id,
|
||||
community_follower::person_id,
|
||||
community_actions::community_id,
|
||||
community_actions::person_id,
|
||||
))
|
||||
.do_update()
|
||||
.set(form)
|
||||
.returning(Self::as_select())
|
||||
.get_result::<Self>(conn)
|
||||
.await
|
||||
}
|
||||
|
@ -399,16 +423,25 @@ impl Followable for CommunityFollower {
|
|||
person_id: PersonId,
|
||||
) -> Result<Self, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
diesel::update(community_follower::table.find((person_id, community_id)))
|
||||
.set(community_follower::state.eq(CommunityFollowerState::Accepted))
|
||||
.get_result::<Self>(conn)
|
||||
.await
|
||||
diesel::update(find_action(
|
||||
community_actions::follow_state,
|
||||
(person_id, community_id),
|
||||
))
|
||||
.set(community_actions::follow_state.eq(Some(CommunityFollowerState::Accepted)))
|
||||
.returning(Self::as_select())
|
||||
.get_result::<Self>(conn)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn unfollow(pool: &mut DbPool<'_>, form: &CommunityFollowerForm) -> Result<usize, Error> {
|
||||
async fn unfollow(
|
||||
pool: &mut DbPool<'_>,
|
||||
form: &CommunityFollowerForm,
|
||||
) -> Result<uplete::Count, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
diesel::delete(community_follower::table.find((form.person_id, form.community_id)))
|
||||
.execute(conn)
|
||||
uplete::new(community_actions::table.find((form.person_id, form.community_id)))
|
||||
.set_null(community_actions::followed)
|
||||
.set_null(community_actions::follow_state)
|
||||
.set_null(community_actions::follow_approver_id)
|
||||
.get_result(conn)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
@ -483,7 +516,7 @@ mod tests {
|
|||
person::{Person, PersonInsertForm},
|
||||
},
|
||||
traits::{Bannable, Crud, Followable, Joinable},
|
||||
utils::build_db_pool_for_tests,
|
||||
utils::{build_db_pool_for_tests, uplete},
|
||||
CommunityVisibility,
|
||||
};
|
||||
use lemmy_utils::error::LemmyResult;
|
||||
|
@ -650,9 +683,9 @@ mod tests {
|
|||
assert_eq!(expected_community_follower, inserted_community_follower);
|
||||
assert_eq!(expected_community_moderator, inserted_bobby_moderator);
|
||||
assert_eq!(expected_community_person_ban, inserted_community_person_ban);
|
||||
assert_eq!(1, ignored_community);
|
||||
assert_eq!(1, left_community);
|
||||
assert_eq!(1, unban);
|
||||
assert_eq!(uplete::Count::only_updated(1), ignored_community);
|
||||
assert_eq!(uplete::Count::only_updated(1), left_community);
|
||||
assert_eq!(uplete::Count::only_deleted(1), unban);
|
||||
// assert_eq!(2, loaded_count);
|
||||
assert_eq!(1, num_deleted);
|
||||
|
||||
|
|
|
@ -1,18 +1,20 @@
|
|||
use crate::{
|
||||
newtypes::{CommunityId, PersonId},
|
||||
schema::{community, community_block},
|
||||
schema::{community, community_actions},
|
||||
source::{
|
||||
community::Community,
|
||||
community_block::{CommunityBlock, CommunityBlockForm},
|
||||
},
|
||||
traits::Blockable,
|
||||
utils::{get_conn, DbPool},
|
||||
utils::{action_query, find_action, get_conn, now, uplete, DbPool},
|
||||
};
|
||||
use diesel::{
|
||||
dsl::{exists, insert_into, not},
|
||||
expression::SelectableHelper,
|
||||
result::Error,
|
||||
select,
|
||||
ExpressionMethods,
|
||||
NullableExpressionMethods,
|
||||
QueryDsl,
|
||||
};
|
||||
use diesel_async::RunQueryDsl;
|
||||
|
@ -25,9 +27,10 @@ impl CommunityBlock {
|
|||
for_community_id: CommunityId,
|
||||
) -> LemmyResult<()> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
select(not(exists(
|
||||
community_block::table.find((for_person_id, for_community_id)),
|
||||
)))
|
||||
select(not(exists(find_action(
|
||||
community_actions::blocked,
|
||||
(for_person_id, for_community_id),
|
||||
))))
|
||||
.get_result::<bool>(conn)
|
||||
.await?
|
||||
.then_some(())
|
||||
|
@ -39,13 +42,13 @@ impl CommunityBlock {
|
|||
person_id: PersonId,
|
||||
) -> Result<Vec<Community>, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
community_block::table
|
||||
action_query(community_actions::blocked)
|
||||
.inner_join(community::table)
|
||||
.select(community::all_columns)
|
||||
.filter(community_block::person_id.eq(person_id))
|
||||
.filter(community_actions::person_id.eq(person_id))
|
||||
.filter(community::deleted.eq(false))
|
||||
.filter(community::removed.eq(false))
|
||||
.order_by(community_block::published)
|
||||
.order_by(community_actions::blocked)
|
||||
.load::<Community>(conn)
|
||||
.await
|
||||
}
|
||||
|
@ -56,24 +59,33 @@ impl Blockable for CommunityBlock {
|
|||
type Form = CommunityBlockForm;
|
||||
async fn block(pool: &mut DbPool<'_>, community_block_form: &Self::Form) -> Result<Self, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
insert_into(community_block::table)
|
||||
let community_block_form = (
|
||||
community_block_form,
|
||||
community_actions::blocked.eq(now().nullable()),
|
||||
);
|
||||
insert_into(community_actions::table)
|
||||
.values(community_block_form)
|
||||
.on_conflict((community_block::person_id, community_block::community_id))
|
||||
.on_conflict((
|
||||
community_actions::person_id,
|
||||
community_actions::community_id,
|
||||
))
|
||||
.do_update()
|
||||
.set(community_block_form)
|
||||
.returning(Self::as_select())
|
||||
.get_result::<Self>(conn)
|
||||
.await
|
||||
}
|
||||
async fn unblock(
|
||||
pool: &mut DbPool<'_>,
|
||||
community_block_form: &Self::Form,
|
||||
) -> Result<usize, Error> {
|
||||
) -> Result<uplete::Count, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
diesel::delete(community_block::table.find((
|
||||
uplete::new(community_actions::table.find((
|
||||
community_block_form.person_id,
|
||||
community_block_form.community_id,
|
||||
)))
|
||||
.execute(conn)
|
||||
.set_null(community_actions::blocked)
|
||||
.get_result(conn)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,18 +1,20 @@
|
|||
use crate::{
|
||||
newtypes::{InstanceId, PersonId},
|
||||
schema::{instance, instance_block},
|
||||
schema::{instance, instance_actions},
|
||||
source::{
|
||||
instance::Instance,
|
||||
instance_block::{InstanceBlock, InstanceBlockForm},
|
||||
},
|
||||
traits::Blockable,
|
||||
utils::{get_conn, DbPool},
|
||||
utils::{action_query, find_action, get_conn, now, uplete, DbPool},
|
||||
};
|
||||
use diesel::{
|
||||
dsl::{exists, insert_into, not},
|
||||
expression::SelectableHelper,
|
||||
result::Error,
|
||||
select,
|
||||
ExpressionMethods,
|
||||
NullableExpressionMethods,
|
||||
QueryDsl,
|
||||
};
|
||||
use diesel_async::RunQueryDsl;
|
||||
|
@ -25,9 +27,10 @@ impl InstanceBlock {
|
|||
for_instance_id: InstanceId,
|
||||
) -> LemmyResult<()> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
select(not(exists(
|
||||
instance_block::table.find((for_person_id, for_instance_id)),
|
||||
)))
|
||||
select(not(exists(find_action(
|
||||
instance_actions::blocked,
|
||||
(for_person_id, for_instance_id),
|
||||
))))
|
||||
.get_result::<bool>(conn)
|
||||
.await?
|
||||
.then_some(())
|
||||
|
@ -39,11 +42,11 @@ impl InstanceBlock {
|
|||
person_id: PersonId,
|
||||
) -> Result<Vec<Instance>, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
instance_block::table
|
||||
action_query(instance_actions::blocked)
|
||||
.inner_join(instance::table)
|
||||
.select(instance::all_columns)
|
||||
.filter(instance_block::person_id.eq(person_id))
|
||||
.order_by(instance_block::published)
|
||||
.filter(instance_actions::person_id.eq(person_id))
|
||||
.order_by(instance_actions::blocked)
|
||||
.load::<Instance>(conn)
|
||||
.await
|
||||
}
|
||||
|
@ -54,24 +57,30 @@ impl Blockable for InstanceBlock {
|
|||
type Form = InstanceBlockForm;
|
||||
async fn block(pool: &mut DbPool<'_>, instance_block_form: &Self::Form) -> Result<Self, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
insert_into(instance_block::table)
|
||||
let instance_block_form = (
|
||||
instance_block_form,
|
||||
instance_actions::blocked.eq(now().nullable()),
|
||||
);
|
||||
insert_into(instance_actions::table)
|
||||
.values(instance_block_form)
|
||||
.on_conflict((instance_block::person_id, instance_block::instance_id))
|
||||
.on_conflict((instance_actions::person_id, instance_actions::instance_id))
|
||||
.do_update()
|
||||
.set(instance_block_form)
|
||||
.returning(Self::as_select())
|
||||
.get_result::<Self>(conn)
|
||||
.await
|
||||
}
|
||||
async fn unblock(
|
||||
pool: &mut DbPool<'_>,
|
||||
instance_block_form: &Self::Form,
|
||||
) -> Result<usize, Error> {
|
||||
) -> Result<uplete::Count, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
diesel::delete(instance_block::table.find((
|
||||
uplete::new(instance_actions::table.find((
|
||||
instance_block_form.person_id,
|
||||
instance_block_form.instance_id,
|
||||
)))
|
||||
.execute(conn)
|
||||
.set_null(instance_actions::blocked)
|
||||
.get_result(conn)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::{
|
||||
newtypes::{CommunityId, DbUrl, LanguageId, LocalUserId, PersonId},
|
||||
schema::{community, community_moderator, local_user, person, registration_application},
|
||||
schema::{community, community_actions, local_user, person, registration_application},
|
||||
source::{
|
||||
actor_language::LocalUserLanguage,
|
||||
local_user::{LocalUser, LocalUserInsertForm, LocalUserUpdateForm},
|
||||
|
@ -8,6 +8,7 @@ use crate::{
|
|||
site::Site,
|
||||
},
|
||||
utils::{
|
||||
action_query,
|
||||
functions::{coalesce, lower},
|
||||
get_conn,
|
||||
now,
|
||||
|
@ -155,55 +156,54 @@ impl LocalUser {
|
|||
) -> Result<UserBackupLists, Error> {
|
||||
use crate::schema::{
|
||||
comment,
|
||||
comment_saved,
|
||||
comment_actions,
|
||||
community,
|
||||
community_block,
|
||||
community_follower,
|
||||
community_actions,
|
||||
instance,
|
||||
instance_block,
|
||||
person_block,
|
||||
instance_actions,
|
||||
person_actions,
|
||||
post,
|
||||
post_saved,
|
||||
post_actions,
|
||||
};
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
|
||||
let followed_communities = community_follower::dsl::community_follower
|
||||
.filter(community_follower::person_id.eq(person_id_))
|
||||
.inner_join(community::table.on(community_follower::community_id.eq(community::id)))
|
||||
.select(community::actor_id)
|
||||
.get_results(conn)
|
||||
.await?;
|
||||
|
||||
let saved_posts = post_saved::dsl::post_saved
|
||||
.filter(post_saved::person_id.eq(person_id_))
|
||||
.inner_join(post::table.on(post_saved::post_id.eq(post::id)))
|
||||
.select(post::ap_id)
|
||||
.get_results(conn)
|
||||
.await?;
|
||||
|
||||
let saved_comments = comment_saved::dsl::comment_saved
|
||||
.filter(comment_saved::person_id.eq(person_id_))
|
||||
.inner_join(comment::table.on(comment_saved::comment_id.eq(comment::id)))
|
||||
.select(comment::ap_id)
|
||||
.get_results(conn)
|
||||
.await?;
|
||||
|
||||
let blocked_communities = community_block::dsl::community_block
|
||||
.filter(community_block::person_id.eq(person_id_))
|
||||
let followed_communities = action_query(community_actions::followed)
|
||||
.filter(community_actions::person_id.eq(person_id_))
|
||||
.inner_join(community::table)
|
||||
.select(community::actor_id)
|
||||
.get_results(conn)
|
||||
.await?;
|
||||
|
||||
let blocked_users = person_block::dsl::person_block
|
||||
.filter(person_block::person_id.eq(person_id_))
|
||||
.inner_join(person::table.on(person_block::target_id.eq(person::id)))
|
||||
let saved_posts = action_query(post_actions::saved)
|
||||
.filter(post_actions::person_id.eq(person_id_))
|
||||
.inner_join(post::table)
|
||||
.select(post::ap_id)
|
||||
.get_results(conn)
|
||||
.await?;
|
||||
|
||||
let saved_comments = action_query(comment_actions::saved)
|
||||
.filter(comment_actions::person_id.eq(person_id_))
|
||||
.inner_join(comment::table)
|
||||
.select(comment::ap_id)
|
||||
.get_results(conn)
|
||||
.await?;
|
||||
|
||||
let blocked_communities = action_query(community_actions::blocked)
|
||||
.filter(community_actions::person_id.eq(person_id_))
|
||||
.inner_join(community::table)
|
||||
.select(community::actor_id)
|
||||
.get_results(conn)
|
||||
.await?;
|
||||
|
||||
let blocked_users = action_query(person_actions::blocked)
|
||||
.filter(person_actions::person_id.eq(person_id_))
|
||||
.inner_join(person::table.on(person_actions::target_id.eq(person::id)))
|
||||
.select(person::actor_id)
|
||||
.get_results(conn)
|
||||
.await?;
|
||||
|
||||
let blocked_instances = instance_block::dsl::instance_block
|
||||
.filter(instance_block::person_id.eq(person_id_))
|
||||
let blocked_instances = action_query(instance_actions::blocked)
|
||||
.filter(instance_actions::person_id.eq(person_id_))
|
||||
.inner_join(instance::table)
|
||||
.select(instance::domain)
|
||||
.get_results(conn)
|
||||
|
@ -270,11 +270,11 @@ impl LocalUser {
|
|||
.order_by(local_user::id)
|
||||
.select(local_user::person_id);
|
||||
|
||||
let mods = community_moderator::table
|
||||
.filter(community_moderator::community_id.eq(for_community_id))
|
||||
.filter(community_moderator::person_id.eq_any(&persons))
|
||||
.order_by(community_moderator::published)
|
||||
.select(community_moderator::person_id);
|
||||
let mods = action_query(community_actions::became_moderator)
|
||||
.filter(community_actions::community_id.eq(for_community_id))
|
||||
.filter(community_actions::person_id.eq_any(&persons))
|
||||
.order_by(community_actions::became_moderator)
|
||||
.select(community_actions::person_id);
|
||||
|
||||
let res = admins.union_all(mods).get_results::<PersonId>(conn).await?;
|
||||
let first_person = res.as_slice().first().ok_or(LemmyErrorType::NotHigherMod)?;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::{
|
||||
diesel::OptionalExtension,
|
||||
newtypes::{CommunityId, DbUrl, InstanceId, PersonId},
|
||||
schema::{comment, community, instance, local_user, person, person_follower, post},
|
||||
schema::{comment, community, instance, local_user, person, person_actions, post},
|
||||
source::person::{
|
||||
Person,
|
||||
PersonFollower,
|
||||
|
@ -10,14 +10,16 @@ use crate::{
|
|||
PersonUpdateForm,
|
||||
},
|
||||
traits::{ApubActor, Crud, Followable},
|
||||
utils::{functions::lower, get_conn, naive_now, DbPool},
|
||||
utils::{action_query, functions::lower, get_conn, naive_now, now, uplete, DbPool},
|
||||
};
|
||||
use diesel::{
|
||||
dsl::{insert_into, not},
|
||||
expression::SelectableHelper,
|
||||
result::Error,
|
||||
CombineDsl,
|
||||
ExpressionMethods,
|
||||
JoinOnDsl,
|
||||
NullableExpressionMethods,
|
||||
QueryDsl,
|
||||
};
|
||||
use diesel_async::RunQueryDsl;
|
||||
|
@ -197,11 +199,13 @@ impl Followable for PersonFollower {
|
|||
type Form = PersonFollowerForm;
|
||||
async fn follow(pool: &mut DbPool<'_>, form: &PersonFollowerForm) -> Result<Self, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
insert_into(person_follower::table)
|
||||
let form = (form, person_actions::followed.eq(now().nullable()));
|
||||
insert_into(person_actions::table)
|
||||
.values(form)
|
||||
.on_conflict((person_follower::follower_id, person_follower::person_id))
|
||||
.on_conflict((person_actions::person_id, person_actions::target_id))
|
||||
.do_update()
|
||||
.set(form)
|
||||
.returning(Self::as_select())
|
||||
.get_result::<Self>(conn)
|
||||
.await
|
||||
}
|
||||
|
@ -211,10 +215,15 @@ impl Followable for PersonFollower {
|
|||
Err(Error::NotFound)
|
||||
}
|
||||
|
||||
async fn unfollow(pool: &mut DbPool<'_>, form: &PersonFollowerForm) -> Result<usize, Error> {
|
||||
async fn unfollow(
|
||||
pool: &mut DbPool<'_>,
|
||||
form: &PersonFollowerForm,
|
||||
) -> Result<uplete::Count, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
diesel::delete(person_follower::table.find((form.follower_id, form.person_id)))
|
||||
.execute(conn)
|
||||
uplete::new(person_actions::table.find((form.follower_id, form.person_id)))
|
||||
.set_null(person_actions::followed)
|
||||
.set_null(person_actions::follow_pending)
|
||||
.get_result(conn)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
@ -225,9 +234,9 @@ impl PersonFollower {
|
|||
for_person_id: PersonId,
|
||||
) -> Result<Vec<Person>, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
person_follower::table
|
||||
.inner_join(person::table.on(person_follower::follower_id.eq(person::id)))
|
||||
.filter(person_follower::person_id.eq(for_person_id))
|
||||
action_query(person_actions::followed)
|
||||
.inner_join(person::table.on(person_actions::person_id.eq(person::id)))
|
||||
.filter(person_actions::target_id.eq(for_person_id))
|
||||
.select(person::all_columns)
|
||||
.load(conn)
|
||||
.await
|
||||
|
@ -243,7 +252,7 @@ mod tests {
|
|||
person::{Person, PersonFollower, PersonFollowerForm, PersonInsertForm, PersonUpdateForm},
|
||||
},
|
||||
traits::{Crud, Followable},
|
||||
utils::build_db_pool_for_tests,
|
||||
utils::{build_db_pool_for_tests, uplete},
|
||||
};
|
||||
use lemmy_utils::error::LemmyResult;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
@ -329,7 +338,7 @@ mod tests {
|
|||
assert_eq!(vec![person_2], followers);
|
||||
|
||||
let unfollow = PersonFollower::unfollow(pool, &follow_form).await?;
|
||||
assert_eq!(1, unfollow);
|
||||
assert_eq!(uplete::Count::only_deleted(1), unfollow);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,19 +1,21 @@
|
|||
use crate::{
|
||||
newtypes::PersonId,
|
||||
schema::{person, person_block},
|
||||
schema::{person, person_actions},
|
||||
source::{
|
||||
person::Person,
|
||||
person_block::{PersonBlock, PersonBlockForm},
|
||||
},
|
||||
traits::Blockable,
|
||||
utils::{get_conn, DbPool},
|
||||
utils::{action_query, find_action, get_conn, now, uplete, DbPool},
|
||||
};
|
||||
use diesel::{
|
||||
dsl::{exists, insert_into, not},
|
||||
expression::SelectableHelper,
|
||||
result::Error,
|
||||
select,
|
||||
ExpressionMethods,
|
||||
JoinOnDsl,
|
||||
NullableExpressionMethods,
|
||||
QueryDsl,
|
||||
};
|
||||
use diesel_async::RunQueryDsl;
|
||||
|
@ -26,9 +28,10 @@ impl PersonBlock {
|
|||
for_recipient_id: PersonId,
|
||||
) -> LemmyResult<()> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
select(not(exists(
|
||||
person_block::table.find((for_person_id, for_recipient_id)),
|
||||
)))
|
||||
select(not(exists(find_action(
|
||||
person_actions::blocked,
|
||||
(for_person_id, for_recipient_id),
|
||||
))))
|
||||
.get_result::<bool>(conn)
|
||||
.await?
|
||||
.then_some(())
|
||||
|
@ -42,15 +45,15 @@ impl PersonBlock {
|
|||
let conn = &mut get_conn(pool).await?;
|
||||
let target_person_alias = diesel::alias!(person as person1);
|
||||
|
||||
person_block::table
|
||||
.inner_join(person::table.on(person_block::person_id.eq(person::id)))
|
||||
action_query(person_actions::blocked)
|
||||
.inner_join(person::table.on(person_actions::person_id.eq(person::id)))
|
||||
.inner_join(
|
||||
target_person_alias.on(person_block::target_id.eq(target_person_alias.field(person::id))),
|
||||
target_person_alias.on(person_actions::target_id.eq(target_person_alias.field(person::id))),
|
||||
)
|
||||
.select(target_person_alias.fields(person::all_columns))
|
||||
.filter(person_block::person_id.eq(person_id))
|
||||
.filter(person_actions::person_id.eq(person_id))
|
||||
.filter(target_person_alias.field(person::deleted).eq(false))
|
||||
.order_by(person_block::published)
|
||||
.order_by(person_actions::blocked)
|
||||
.load::<Person>(conn)
|
||||
.await
|
||||
}
|
||||
|
@ -64,20 +67,29 @@ impl Blockable for PersonBlock {
|
|||
person_block_form: &PersonBlockForm,
|
||||
) -> Result<Self, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
insert_into(person_block::table)
|
||||
let person_block_form = (
|
||||
person_block_form,
|
||||
person_actions::blocked.eq(now().nullable()),
|
||||
);
|
||||
insert_into(person_actions::table)
|
||||
.values(person_block_form)
|
||||
.on_conflict((person_block::person_id, person_block::target_id))
|
||||
.on_conflict((person_actions::person_id, person_actions::target_id))
|
||||
.do_update()
|
||||
.set(person_block_form)
|
||||
.returning(Self::as_select())
|
||||
.get_result::<Self>(conn)
|
||||
.await
|
||||
}
|
||||
async fn unblock(pool: &mut DbPool<'_>, person_block_form: &Self::Form) -> Result<usize, Error> {
|
||||
async fn unblock(
|
||||
pool: &mut DbPool<'_>,
|
||||
person_block_form: &Self::Form,
|
||||
) -> Result<uplete::Count, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
diesel::delete(
|
||||
person_block::table.find((person_block_form.person_id, person_block_form.target_id)),
|
||||
uplete::new(
|
||||
person_actions::table.find((person_block_form.person_id, person_block_form.target_id)),
|
||||
)
|
||||
.execute(conn)
|
||||
.set_null(person_actions::blocked)
|
||||
.get_result(conn)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::{
|
||||
diesel::{BoolExpressionMethods, OptionalExtension},
|
||||
newtypes::{CommunityId, DbUrl, PersonId, PostId},
|
||||
schema::{community, person, post, post_hide, post_like, post_read, post_saved},
|
||||
schema::{community, person, post, post_actions},
|
||||
source::post::{
|
||||
Post,
|
||||
PostHide,
|
||||
|
@ -21,6 +21,7 @@ use crate::{
|
|||
get_conn,
|
||||
naive_now,
|
||||
now,
|
||||
uplete,
|
||||
DbPool,
|
||||
DELETED_REPLACEMENT_TEXT,
|
||||
FETCH_LIMIT_MAX,
|
||||
|
@ -32,9 +33,11 @@ use ::url::Url;
|
|||
use chrono::{DateTime, Utc};
|
||||
use diesel::{
|
||||
dsl::{count, insert_into, not},
|
||||
expression::SelectableHelper,
|
||||
result::Error,
|
||||
DecoratableTarget,
|
||||
ExpressionMethods,
|
||||
NullableExpressionMethods,
|
||||
QueryDsl,
|
||||
TextExpressionMethods,
|
||||
};
|
||||
|
@ -279,11 +282,13 @@ impl Likeable for PostLike {
|
|||
type IdType = PostId;
|
||||
async fn like(pool: &mut DbPool<'_>, post_like_form: &PostLikeForm) -> Result<Self, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
insert_into(post_like::table)
|
||||
let post_like_form = (post_like_form, post_actions::liked.eq(now().nullable()));
|
||||
insert_into(post_actions::table)
|
||||
.values(post_like_form)
|
||||
.on_conflict((post_like::post_id, post_like::person_id))
|
||||
.on_conflict((post_actions::post_id, post_actions::person_id))
|
||||
.do_update()
|
||||
.set(post_like_form)
|
||||
.returning(Self::as_select())
|
||||
.get_result::<Self>(conn)
|
||||
.await
|
||||
}
|
||||
|
@ -291,10 +296,12 @@ impl Likeable for PostLike {
|
|||
pool: &mut DbPool<'_>,
|
||||
person_id: PersonId,
|
||||
post_id: PostId,
|
||||
) -> Result<usize, Error> {
|
||||
) -> Result<uplete::Count, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
diesel::delete(post_like::table.find((person_id, post_id)))
|
||||
.execute(conn)
|
||||
uplete::new(post_actions::table.find((person_id, post_id)))
|
||||
.set_null(post_actions::like_score)
|
||||
.set_null(post_actions::liked)
|
||||
.get_result(conn)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
@ -304,18 +311,24 @@ impl Saveable for PostSaved {
|
|||
type Form = PostSavedForm;
|
||||
async fn save(pool: &mut DbPool<'_>, post_saved_form: &PostSavedForm) -> Result<Self, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
insert_into(post_saved::table)
|
||||
let post_saved_form = (post_saved_form, post_actions::saved.eq(now().nullable()));
|
||||
insert_into(post_actions::table)
|
||||
.values(post_saved_form)
|
||||
.on_conflict((post_saved::post_id, post_saved::person_id))
|
||||
.on_conflict((post_actions::post_id, post_actions::person_id))
|
||||
.do_update()
|
||||
.set(post_saved_form)
|
||||
.returning(Self::as_select())
|
||||
.get_result::<Self>(conn)
|
||||
.await
|
||||
}
|
||||
async fn unsave(pool: &mut DbPool<'_>, post_saved_form: &PostSavedForm) -> Result<usize, Error> {
|
||||
async fn unsave(
|
||||
pool: &mut DbPool<'_>,
|
||||
post_saved_form: &PostSavedForm,
|
||||
) -> Result<uplete::Count, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
diesel::delete(post_saved::table.find((post_saved_form.person_id, post_saved_form.post_id)))
|
||||
.execute(conn)
|
||||
uplete::new(post_actions::table.find((post_saved_form.person_id, post_saved_form.post_id)))
|
||||
.set_null(post_actions::saved)
|
||||
.get_result(conn)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
@ -330,14 +343,22 @@ impl PostRead {
|
|||
|
||||
let forms = post_ids
|
||||
.iter()
|
||||
.map(|post_id| PostReadForm {
|
||||
post_id: *post_id,
|
||||
person_id,
|
||||
.map(|post_id| {
|
||||
(
|
||||
PostReadForm {
|
||||
post_id: *post_id,
|
||||
person_id,
|
||||
},
|
||||
post_actions::read.eq(now().nullable()),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<PostReadForm>>();
|
||||
insert_into(post_read::table)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
insert_into(post_actions::table)
|
||||
.values(forms)
|
||||
.on_conflict_do_nothing()
|
||||
.on_conflict((post_actions::person_id, post_actions::post_id))
|
||||
.do_update()
|
||||
.set(post_actions::read.eq(now().nullable()))
|
||||
.execute(conn)
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntMarkPostAsRead)
|
||||
|
@ -347,15 +368,16 @@ impl PostRead {
|
|||
pool: &mut DbPool<'_>,
|
||||
post_ids: &[PostId],
|
||||
person_id_: PersonId,
|
||||
) -> LemmyResult<usize> {
|
||||
) -> LemmyResult<uplete::Count> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
|
||||
diesel::delete(
|
||||
post_read::table
|
||||
.filter(post_read::post_id.eq_any(post_ids))
|
||||
.filter(post_read::person_id.eq(person_id_)),
|
||||
uplete::new(
|
||||
post_actions::table
|
||||
.filter(post_actions::post_id.eq_any(post_ids.to_vec()))
|
||||
.filter(post_actions::person_id.eq(person_id_)),
|
||||
)
|
||||
.execute(conn)
|
||||
.set_null(post_actions::read)
|
||||
.get_result(conn)
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntMarkPostAsRead)
|
||||
}
|
||||
|
@ -371,11 +393,18 @@ impl PostHide {
|
|||
|
||||
let forms = post_ids
|
||||
.into_iter()
|
||||
.map(|post_id| PostHideForm { post_id, person_id })
|
||||
.collect::<Vec<PostHideForm>>();
|
||||
insert_into(post_hide::table)
|
||||
.map(|post_id| {
|
||||
(
|
||||
PostHideForm { post_id, person_id },
|
||||
post_actions::hidden.eq(now().nullable()),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
insert_into(post_actions::table)
|
||||
.values(forms)
|
||||
.on_conflict_do_nothing()
|
||||
.on_conflict((post_actions::person_id, post_actions::post_id))
|
||||
.do_update()
|
||||
.set(post_actions::hidden.eq(now().nullable()))
|
||||
.execute(conn)
|
||||
.await
|
||||
}
|
||||
|
@ -384,15 +413,16 @@ impl PostHide {
|
|||
pool: &mut DbPool<'_>,
|
||||
post_id_: HashSet<PostId>,
|
||||
person_id_: PersonId,
|
||||
) -> Result<usize, Error> {
|
||||
) -> Result<uplete::Count, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
|
||||
diesel::delete(
|
||||
post_hide::table
|
||||
.filter(post_hide::post_id.eq_any(post_id_))
|
||||
.filter(post_hide::person_id.eq(person_id_)),
|
||||
uplete::new(
|
||||
post_actions::table
|
||||
.filter(post_actions::post_id.eq_any(post_id_))
|
||||
.filter(post_actions::person_id.eq(person_id_)),
|
||||
)
|
||||
.execute(conn)
|
||||
.set_null(post_actions::hidden)
|
||||
.get_result(conn)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
@ -417,7 +447,7 @@ mod tests {
|
|||
},
|
||||
},
|
||||
traits::{Crud, Likeable, Saveable},
|
||||
utils::build_db_pool_for_tests,
|
||||
utils::{build_db_pool_for_tests, uplete},
|
||||
};
|
||||
use chrono::DateTime;
|
||||
use lemmy_utils::error::LemmyResult;
|
||||
|
@ -545,16 +575,16 @@ mod tests {
|
|||
assert_eq!(1, scheduled_post_count);
|
||||
|
||||
let like_removed = PostLike::remove(pool, inserted_person.id, inserted_post.id).await?;
|
||||
assert_eq!(1, like_removed);
|
||||
assert_eq!(uplete::Count::only_updated(1), like_removed);
|
||||
let saved_removed = PostSaved::unsave(pool, &post_saved_form).await?;
|
||||
assert_eq!(1, saved_removed);
|
||||
assert_eq!(uplete::Count::only_updated(1), saved_removed);
|
||||
let read_removed = PostRead::mark_as_unread(
|
||||
pool,
|
||||
&[inserted_post.id, inserted_post2.id],
|
||||
inserted_person.id,
|
||||
)
|
||||
.await?;
|
||||
assert_eq!(2, read_removed);
|
||||
assert_eq!(uplete::Count::only_deleted(2), read_removed);
|
||||
|
||||
let num_deleted = Post::delete(pool, inserted_post.id).await?
|
||||
+ Post::delete(pool, inserted_post2.id).await?
|
||||
|
|
|
@ -30,11 +30,11 @@ pub mod sensitive;
|
|||
pub mod schema;
|
||||
#[cfg(feature = "full")]
|
||||
pub mod aliases {
|
||||
use crate::schema::{community_moderator, person};
|
||||
use crate::schema::{community_actions, person};
|
||||
diesel::alias!(
|
||||
community_actions as creator_community_actions: CreatorCommunityActions,
|
||||
person as person1: Person1,
|
||||
person as person2: Person2,
|
||||
community_moderator as community_moderator1: CommunityModerator1
|
||||
);
|
||||
}
|
||||
pub mod source;
|
||||
|
|
|
@ -110,6 +110,16 @@ diesel::table! {
|
|||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
comment_actions (person_id, comment_id) {
|
||||
person_id -> Int4,
|
||||
comment_id -> Int4,
|
||||
like_score -> Nullable<Int2>,
|
||||
liked -> Nullable<Timestamptz>,
|
||||
saved -> Nullable<Timestamptz>,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
comment_aggregates (comment_id) {
|
||||
comment_id -> Int4,
|
||||
|
@ -123,15 +133,6 @@ diesel::table! {
|
|||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
comment_like (person_id, comment_id) {
|
||||
person_id -> Int4,
|
||||
comment_id -> Int4,
|
||||
score -> Int2,
|
||||
published -> Timestamptz,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
comment_reply (id) {
|
||||
id -> Int4,
|
||||
|
@ -156,14 +157,6 @@ diesel::table! {
|
|||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
comment_saved (person_id, comment_id) {
|
||||
comment_id -> Int4,
|
||||
person_id -> Int4,
|
||||
published -> Timestamptz,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
use diesel::sql_types::*;
|
||||
use super::sql_types::CommunityVisibility;
|
||||
|
@ -205,6 +198,23 @@ diesel::table! {
|
|||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
use diesel::sql_types::*;
|
||||
use super::sql_types::CommunityFollowerState;
|
||||
|
||||
community_actions (person_id, community_id) {
|
||||
community_id -> Int4,
|
||||
person_id -> Int4,
|
||||
followed -> Nullable<Timestamptz>,
|
||||
follow_state -> Nullable<CommunityFollowerState>,
|
||||
follow_approver_id -> Nullable<Int4>,
|
||||
blocked -> Nullable<Timestamptz>,
|
||||
became_moderator -> Nullable<Timestamptz>,
|
||||
received_ban -> Nullable<Timestamptz>,
|
||||
ban_expires -> Nullable<Timestamptz>,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
community_aggregates (community_id) {
|
||||
community_id -> Int4,
|
||||
|
@ -221,27 +231,6 @@ diesel::table! {
|
|||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
community_block (person_id, community_id) {
|
||||
person_id -> Int4,
|
||||
community_id -> Int4,
|
||||
published -> Timestamptz,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
use diesel::sql_types::*;
|
||||
use super::sql_types::CommunityFollowerState;
|
||||
|
||||
community_follower (person_id, community_id) {
|
||||
community_id -> Int4,
|
||||
person_id -> Int4,
|
||||
published -> Timestamptz,
|
||||
state -> CommunityFollowerState,
|
||||
approver_id -> Nullable<Int4>,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
community_language (community_id, language_id) {
|
||||
community_id -> Int4,
|
||||
|
@ -249,23 +238,6 @@ diesel::table! {
|
|||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
community_moderator (person_id, community_id) {
|
||||
community_id -> Int4,
|
||||
person_id -> Int4,
|
||||
published -> Timestamptz,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
community_person_ban (person_id, community_id) {
|
||||
community_id -> Int4,
|
||||
person_id -> Int4,
|
||||
published -> Timestamptz,
|
||||
expires -> Nullable<Timestamptz>,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
custom_emoji (id) {
|
||||
id -> Int4,
|
||||
|
@ -347,10 +319,10 @@ diesel::table! {
|
|||
}
|
||||
|
||||
diesel::table! {
|
||||
instance_block (person_id, instance_id) {
|
||||
instance_actions (person_id, instance_id) {
|
||||
person_id -> Int4,
|
||||
instance_id -> Int4,
|
||||
published -> Timestamptz,
|
||||
blocked -> Nullable<Timestamptz>,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -703,6 +675,16 @@ diesel::table! {
|
|||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
person_actions (person_id, target_id) {
|
||||
target_id -> Int4,
|
||||
person_id -> Int4,
|
||||
followed -> Nullable<Timestamptz>,
|
||||
follow_pending -> Nullable<Bool>,
|
||||
blocked -> Nullable<Timestamptz>,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
person_aggregates (person_id) {
|
||||
person_id -> Int4,
|
||||
|
@ -720,23 +702,6 @@ diesel::table! {
|
|||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
person_block (person_id, target_id) {
|
||||
person_id -> Int4,
|
||||
target_id -> Int4,
|
||||
published -> Timestamptz,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
person_follower (follower_id, person_id) {
|
||||
person_id -> Int4,
|
||||
follower_id -> Int4,
|
||||
published -> Timestamptz,
|
||||
pending -> Bool,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
person_mention (id) {
|
||||
id -> Int4,
|
||||
|
@ -747,15 +712,6 @@ diesel::table! {
|
|||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
person_post_aggregates (person_id, post_id) {
|
||||
person_id -> Int4,
|
||||
post_id -> Int4,
|
||||
read_comments -> Int8,
|
||||
published -> Timestamptz,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
post (id) {
|
||||
id -> Int4,
|
||||
|
@ -788,6 +744,20 @@ diesel::table! {
|
|||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
post_actions (person_id, post_id) {
|
||||
post_id -> Int4,
|
||||
person_id -> Int4,
|
||||
read -> Nullable<Timestamptz>,
|
||||
read_comments -> Nullable<Timestamptz>,
|
||||
read_comments_amount -> Nullable<Int8>,
|
||||
saved -> Nullable<Timestamptz>,
|
||||
liked -> Nullable<Timestamptz>,
|
||||
like_score -> Nullable<Int2>,
|
||||
hidden -> Nullable<Timestamptz>,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
post_aggregates (post_id) {
|
||||
post_id -> Int4,
|
||||
|
@ -810,31 +780,6 @@ diesel::table! {
|
|||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
post_hide (person_id, post_id) {
|
||||
post_id -> Int4,
|
||||
person_id -> Int4,
|
||||
published -> Timestamptz,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
post_like (person_id, post_id) {
|
||||
post_id -> Int4,
|
||||
person_id -> Int4,
|
||||
score -> Int2,
|
||||
published -> Timestamptz,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
post_read (person_id, post_id) {
|
||||
post_id -> Int4,
|
||||
person_id -> Int4,
|
||||
published -> Timestamptz,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
post_report (id) {
|
||||
id -> Int4,
|
||||
|
@ -852,14 +797,6 @@ diesel::table! {
|
|||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
post_saved (person_id, post_id) {
|
||||
post_id -> Int4,
|
||||
person_id -> Int4,
|
||||
published -> Timestamptz,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
private_message (id) {
|
||||
id -> Int4,
|
||||
|
@ -1003,32 +940,24 @@ diesel::joinable!(admin_purge_post -> person (admin_person_id));
|
|||
diesel::joinable!(comment -> language (language_id));
|
||||
diesel::joinable!(comment -> person (creator_id));
|
||||
diesel::joinable!(comment -> post (post_id));
|
||||
diesel::joinable!(comment_actions -> comment (comment_id));
|
||||
diesel::joinable!(comment_actions -> person (person_id));
|
||||
diesel::joinable!(comment_aggregates -> comment (comment_id));
|
||||
diesel::joinable!(comment_like -> comment (comment_id));
|
||||
diesel::joinable!(comment_like -> person (person_id));
|
||||
diesel::joinable!(comment_reply -> comment (comment_id));
|
||||
diesel::joinable!(comment_reply -> person (recipient_id));
|
||||
diesel::joinable!(comment_report -> comment (comment_id));
|
||||
diesel::joinable!(comment_saved -> comment (comment_id));
|
||||
diesel::joinable!(comment_saved -> person (person_id));
|
||||
diesel::joinable!(community -> instance (instance_id));
|
||||
diesel::joinable!(community_actions -> community (community_id));
|
||||
diesel::joinable!(community_aggregates -> community (community_id));
|
||||
diesel::joinable!(community_block -> community (community_id));
|
||||
diesel::joinable!(community_block -> person (person_id));
|
||||
diesel::joinable!(community_follower -> community (community_id));
|
||||
diesel::joinable!(community_language -> community (community_id));
|
||||
diesel::joinable!(community_language -> language (language_id));
|
||||
diesel::joinable!(community_moderator -> community (community_id));
|
||||
diesel::joinable!(community_moderator -> person (person_id));
|
||||
diesel::joinable!(community_person_ban -> community (community_id));
|
||||
diesel::joinable!(community_person_ban -> person (person_id));
|
||||
diesel::joinable!(custom_emoji_keyword -> custom_emoji (custom_emoji_id));
|
||||
diesel::joinable!(email_verification -> local_user (local_user_id));
|
||||
diesel::joinable!(federation_allowlist -> instance (instance_id));
|
||||
diesel::joinable!(federation_blocklist -> instance (instance_id));
|
||||
diesel::joinable!(federation_queue_state -> instance (instance_id));
|
||||
diesel::joinable!(instance_block -> instance (instance_id));
|
||||
diesel::joinable!(instance_block -> person (person_id));
|
||||
diesel::joinable!(instance_actions -> instance (instance_id));
|
||||
diesel::joinable!(instance_actions -> person (person_id));
|
||||
diesel::joinable!(local_image -> local_user (local_user_id));
|
||||
diesel::joinable!(local_site -> site (site_id));
|
||||
diesel::joinable!(local_site_rate_limit -> local_site (local_site_id));
|
||||
|
@ -1060,24 +989,16 @@ diesel::joinable!(person_aggregates -> person (person_id));
|
|||
diesel::joinable!(person_ban -> person (person_id));
|
||||
diesel::joinable!(person_mention -> comment (comment_id));
|
||||
diesel::joinable!(person_mention -> person (recipient_id));
|
||||
diesel::joinable!(person_post_aggregates -> person (person_id));
|
||||
diesel::joinable!(person_post_aggregates -> post (post_id));
|
||||
diesel::joinable!(post -> community (community_id));
|
||||
diesel::joinable!(post -> language (language_id));
|
||||
diesel::joinable!(post -> person (creator_id));
|
||||
diesel::joinable!(post_actions -> person (person_id));
|
||||
diesel::joinable!(post_actions -> post (post_id));
|
||||
diesel::joinable!(post_aggregates -> community (community_id));
|
||||
diesel::joinable!(post_aggregates -> instance (instance_id));
|
||||
diesel::joinable!(post_aggregates -> person (creator_id));
|
||||
diesel::joinable!(post_aggregates -> post (post_id));
|
||||
diesel::joinable!(post_hide -> person (person_id));
|
||||
diesel::joinable!(post_hide -> post (post_id));
|
||||
diesel::joinable!(post_like -> person (person_id));
|
||||
diesel::joinable!(post_like -> post (post_id));
|
||||
diesel::joinable!(post_read -> person (person_id));
|
||||
diesel::joinable!(post_read -> post (post_id));
|
||||
diesel::joinable!(post_report -> post (post_id));
|
||||
diesel::joinable!(post_saved -> person (person_id));
|
||||
diesel::joinable!(post_saved -> post (post_id));
|
||||
diesel::joinable!(private_message_report -> private_message (private_message_id));
|
||||
diesel::joinable!(registration_application -> local_user (local_user_id));
|
||||
diesel::joinable!(registration_application -> person (admin_id));
|
||||
|
@ -1093,18 +1014,14 @@ diesel::allow_tables_to_appear_in_same_query!(
|
|||
admin_purge_post,
|
||||
captcha_answer,
|
||||
comment,
|
||||
comment_actions,
|
||||
comment_aggregates,
|
||||
comment_like,
|
||||
comment_reply,
|
||||
comment_report,
|
||||
comment_saved,
|
||||
community,
|
||||
community_actions,
|
||||
community_aggregates,
|
||||
community_block,
|
||||
community_follower,
|
||||
community_language,
|
||||
community_moderator,
|
||||
community_person_ban,
|
||||
custom_emoji,
|
||||
custom_emoji_keyword,
|
||||
email_verification,
|
||||
|
@ -1113,7 +1030,7 @@ diesel::allow_tables_to_appear_in_same_query!(
|
|||
federation_queue_state,
|
||||
image_details,
|
||||
instance,
|
||||
instance_block,
|
||||
instance_actions,
|
||||
language,
|
||||
local_image,
|
||||
local_site,
|
||||
|
@ -1138,19 +1055,14 @@ diesel::allow_tables_to_appear_in_same_query!(
|
|||
oauth_provider,
|
||||
password_reset_request,
|
||||
person,
|
||||
person_actions,
|
||||
person_aggregates,
|
||||
person_ban,
|
||||
person_block,
|
||||
person_follower,
|
||||
person_mention,
|
||||
person_post_aggregates,
|
||||
post,
|
||||
post_actions,
|
||||
post_aggregates,
|
||||
post_hide,
|
||||
post_like,
|
||||
post_read,
|
||||
post_report,
|
||||
post_saved,
|
||||
private_message,
|
||||
private_message_report,
|
||||
received_activity,
|
||||
|
|
|
@ -2,9 +2,11 @@
|
|||
use crate::newtypes::LtreeDef;
|
||||
use crate::newtypes::{CommentId, DbUrl, LanguageId, PersonId, PostId};
|
||||
#[cfg(feature = "full")]
|
||||
use crate::schema::{comment, comment_like, comment_saved};
|
||||
use crate::schema::{comment, comment_actions};
|
||||
use chrono::{DateTime, Utc};
|
||||
#[cfg(feature = "full")]
|
||||
use diesel::{dsl, expression_methods::NullableExpressionMethods};
|
||||
#[cfg(feature = "full")]
|
||||
use diesel_ltree::Ltree;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::skip_serializing_none;
|
||||
|
@ -97,22 +99,27 @@ pub struct CommentUpdateForm {
|
|||
derive(Identifiable, Queryable, Selectable, Associations)
|
||||
)]
|
||||
#[cfg_attr(feature = "full", diesel(belongs_to(crate::source::comment::Comment)))]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = comment_like))]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = comment_actions))]
|
||||
#[cfg_attr(feature = "full", diesel(primary_key(person_id, comment_id)))]
|
||||
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
|
||||
pub struct CommentLike {
|
||||
pub person_id: PersonId,
|
||||
pub comment_id: CommentId,
|
||||
#[cfg_attr(feature = "full", diesel(select_expression = comment_actions::like_score.assume_not_null()))]
|
||||
#[cfg_attr(feature = "full", diesel(select_expression_type = dsl::AssumeNotNull<comment_actions::like_score>))]
|
||||
pub score: i16,
|
||||
#[cfg_attr(feature = "full", diesel(select_expression = comment_actions::liked.assume_not_null()))]
|
||||
#[cfg_attr(feature = "full", diesel(select_expression_type = dsl::AssumeNotNull<comment_actions::liked>))]
|
||||
pub published: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = comment_like))]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = comment_actions))]
|
||||
pub struct CommentLikeForm {
|
||||
pub person_id: PersonId,
|
||||
pub comment_id: CommentId,
|
||||
#[cfg_attr(feature = "full", diesel(column_name = like_score))]
|
||||
pub score: i16,
|
||||
}
|
||||
|
||||
|
@ -122,17 +129,19 @@ pub struct CommentLikeForm {
|
|||
derive(Identifiable, Queryable, Selectable, Associations)
|
||||
)]
|
||||
#[cfg_attr(feature = "full", diesel(belongs_to(crate::source::comment::Comment)))]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = comment_saved))]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = comment_actions))]
|
||||
#[cfg_attr(feature = "full", diesel(primary_key(person_id, comment_id)))]
|
||||
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
|
||||
pub struct CommentSaved {
|
||||
pub comment_id: CommentId,
|
||||
pub person_id: PersonId,
|
||||
#[cfg_attr(feature = "full", diesel(select_expression = comment_actions::saved.assume_not_null()))]
|
||||
#[cfg_attr(feature = "full", diesel(select_expression_type = dsl::AssumeNotNull<comment_actions::saved>))]
|
||||
pub published: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = comment_saved))]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = comment_actions))]
|
||||
pub struct CommentSavedForm {
|
||||
pub comment_id: CommentId,
|
||||
pub person_id: PersonId,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#[cfg(feature = "full")]
|
||||
use crate::schema::{community, community_follower, community_moderator, community_person_ban};
|
||||
use crate::schema::{community, community_actions};
|
||||
use crate::{
|
||||
newtypes::{CommunityId, DbUrl, InstanceId, PersonId},
|
||||
sensitive::SensitiveString,
|
||||
|
@ -7,6 +7,8 @@ use crate::{
|
|||
CommunityVisibility,
|
||||
};
|
||||
use chrono::{DateTime, Utc};
|
||||
#[cfg(feature = "full")]
|
||||
use diesel::{dsl, expression_methods::NullableExpressionMethods};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::skip_serializing_none;
|
||||
use strum::{Display, EnumString};
|
||||
|
@ -163,18 +165,20 @@ pub struct CommunityUpdateForm {
|
|||
feature = "full",
|
||||
diesel(belongs_to(crate::source::community::Community))
|
||||
)]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = community_moderator))]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = community_actions))]
|
||||
#[cfg_attr(feature = "full", diesel(primary_key(person_id, community_id)))]
|
||||
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
|
||||
pub struct CommunityModerator {
|
||||
pub community_id: CommunityId,
|
||||
pub person_id: PersonId,
|
||||
#[cfg_attr(feature = "full", diesel(select_expression = community_actions::became_moderator.assume_not_null()))]
|
||||
#[cfg_attr(feature = "full", diesel(select_expression_type = dsl::AssumeNotNull<community_actions::became_moderator>))]
|
||||
pub published: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = community_moderator))]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = community_actions))]
|
||||
pub struct CommunityModeratorForm {
|
||||
pub community_id: CommunityId,
|
||||
pub person_id: PersonId,
|
||||
|
@ -189,22 +193,26 @@ pub struct CommunityModeratorForm {
|
|||
feature = "full",
|
||||
diesel(belongs_to(crate::source::community::Community))
|
||||
)]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = community_person_ban))]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = community_actions))]
|
||||
#[cfg_attr(feature = "full", diesel(primary_key(person_id, community_id)))]
|
||||
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
|
||||
pub struct CommunityPersonBan {
|
||||
pub community_id: CommunityId,
|
||||
pub person_id: PersonId,
|
||||
#[cfg_attr(feature = "full", diesel(select_expression = community_actions::received_ban.assume_not_null()))]
|
||||
#[cfg_attr(feature = "full", diesel(select_expression_type = dsl::AssumeNotNull<community_actions::received_ban>))]
|
||||
pub published: DateTime<Utc>,
|
||||
#[cfg_attr(feature = "full", diesel(column_name = ban_expires))]
|
||||
pub expires: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = community_person_ban))]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = community_actions))]
|
||||
pub struct CommunityPersonBanForm {
|
||||
pub community_id: CommunityId,
|
||||
pub person_id: PersonId,
|
||||
#[cfg_attr(feature = "full", diesel(column_name = ban_expires))]
|
||||
pub expires: Option<Option<DateTime<Utc>>>,
|
||||
}
|
||||
|
||||
|
@ -231,25 +239,32 @@ pub enum CommunityFollowerState {
|
|||
feature = "full",
|
||||
diesel(belongs_to(crate::source::community::Community))
|
||||
)]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = community_follower))]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = community_actions))]
|
||||
#[cfg_attr(feature = "full", diesel(primary_key(person_id, community_id)))]
|
||||
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
|
||||
pub struct CommunityFollower {
|
||||
pub community_id: CommunityId,
|
||||
pub person_id: PersonId,
|
||||
#[cfg_attr(feature = "full", diesel(select_expression = community_actions::followed.assume_not_null()))]
|
||||
#[cfg_attr(feature = "full", diesel(select_expression_type = dsl::AssumeNotNull<community_actions::followed>))]
|
||||
pub published: DateTime<Utc>,
|
||||
#[cfg_attr(feature = "full", diesel(select_expression = community_actions::follow_state.assume_not_null()))]
|
||||
#[cfg_attr(feature = "full", diesel(select_expression_type = dsl::AssumeNotNull<community_actions::follow_state>))]
|
||||
pub state: CommunityFollowerState,
|
||||
#[cfg_attr(feature = "full", diesel(column_name = follow_approver_id))]
|
||||
pub approver_id: Option<PersonId>,
|
||||
}
|
||||
|
||||
#[derive(Clone, derive_new::new)]
|
||||
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = community_follower))]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = community_actions))]
|
||||
pub struct CommunityFollowerForm {
|
||||
pub community_id: CommunityId,
|
||||
pub person_id: PersonId,
|
||||
#[new(default)]
|
||||
#[cfg_attr(feature = "full", diesel(column_name = follow_state))]
|
||||
pub state: Option<CommunityFollowerState>,
|
||||
#[new(default)]
|
||||
#[cfg_attr(feature = "full", diesel(column_name = follow_approver_id))]
|
||||
pub approver_id: Option<PersonId>,
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
use crate::newtypes::{CommunityId, PersonId};
|
||||
#[cfg(feature = "full")]
|
||||
use crate::schema::community_block;
|
||||
use crate::schema::community_actions;
|
||||
use chrono::{DateTime, Utc};
|
||||
#[cfg(feature = "full")]
|
||||
use diesel::{dsl, expression_methods::NullableExpressionMethods};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
|
||||
|
@ -13,17 +15,19 @@ use serde::{Deserialize, Serialize};
|
|||
feature = "full",
|
||||
diesel(belongs_to(crate::source::community::Community))
|
||||
)]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = community_block))]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = community_actions))]
|
||||
#[cfg_attr(feature = "full", diesel(primary_key(person_id, community_id)))]
|
||||
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
|
||||
pub struct CommunityBlock {
|
||||
pub person_id: PersonId,
|
||||
pub community_id: CommunityId,
|
||||
#[cfg_attr(feature = "full", diesel(select_expression = community_actions::blocked.assume_not_null()))]
|
||||
#[cfg_attr(feature = "full", diesel(select_expression_type = dsl::AssumeNotNull<community_actions::blocked>))]
|
||||
pub published: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = community_block))]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = community_actions))]
|
||||
pub struct CommunityBlockForm {
|
||||
pub person_id: PersonId,
|
||||
pub community_id: CommunityId,
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
use crate::newtypes::{InstanceId, PersonId};
|
||||
#[cfg(feature = "full")]
|
||||
use crate::schema::instance_block;
|
||||
use crate::schema::instance_actions;
|
||||
use chrono::{DateTime, Utc};
|
||||
#[cfg(feature = "full")]
|
||||
use diesel::{dsl, expression_methods::NullableExpressionMethods};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
|
||||
|
@ -13,17 +15,19 @@ use serde::{Deserialize, Serialize};
|
|||
feature = "full",
|
||||
diesel(belongs_to(crate::source::instance::Instance))
|
||||
)]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = instance_block))]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = instance_actions))]
|
||||
#[cfg_attr(feature = "full", diesel(primary_key(person_id, instance_id)))]
|
||||
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
|
||||
pub struct InstanceBlock {
|
||||
pub person_id: PersonId,
|
||||
pub instance_id: InstanceId,
|
||||
#[cfg_attr(feature = "full", diesel(select_expression = instance_actions::blocked.assume_not_null()))]
|
||||
#[cfg_attr(feature = "full", diesel(select_expression_type = dsl::AssumeNotNull<instance_actions::blocked>))]
|
||||
pub published: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = instance_block))]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = instance_actions))]
|
||||
pub struct InstanceBlockForm {
|
||||
pub person_id: PersonId,
|
||||
pub instance_id: InstanceId,
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
#[cfg(feature = "full")]
|
||||
use crate::schema::{person, person_follower};
|
||||
use crate::schema::{person, person_actions};
|
||||
use crate::{
|
||||
newtypes::{DbUrl, InstanceId, PersonId},
|
||||
sensitive::SensitiveString,
|
||||
source::placeholder_apub_url,
|
||||
};
|
||||
use chrono::{DateTime, Utc};
|
||||
#[cfg(feature = "full")]
|
||||
use diesel::{dsl, expression_methods::NullableExpressionMethods};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::skip_serializing_none;
|
||||
#[cfg(feature = "full")]
|
||||
|
@ -133,21 +135,30 @@ pub struct PersonUpdateForm {
|
|||
derive(Identifiable, Queryable, Selectable, Associations)
|
||||
)]
|
||||
#[cfg_attr(feature = "full", diesel(belongs_to(crate::source::person::Person)))]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = person_follower))]
|
||||
#[cfg_attr(feature = "full", diesel(primary_key(follower_id, person_id)))]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = person_actions))]
|
||||
#[cfg_attr(feature = "full", diesel(primary_key(person_id, target_id)))]
|
||||
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
|
||||
pub struct PersonFollower {
|
||||
#[cfg_attr(feature = "full", diesel(column_name = target_id))]
|
||||
pub person_id: PersonId,
|
||||
#[cfg_attr(feature = "full", diesel(column_name = person_id))]
|
||||
pub follower_id: PersonId,
|
||||
#[cfg_attr(feature = "full", diesel(select_expression = person_actions::followed.assume_not_null()))]
|
||||
#[cfg_attr(feature = "full", diesel(select_expression_type = dsl::AssumeNotNull<person_actions::followed>))]
|
||||
pub published: DateTime<Utc>,
|
||||
#[cfg_attr(feature = "full", diesel(select_expression = person_actions::follow_pending.assume_not_null()))]
|
||||
#[cfg_attr(feature = "full", diesel(select_expression_type = dsl::AssumeNotNull<person_actions::follow_pending>))]
|
||||
pub pending: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = person_follower))]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = person_actions))]
|
||||
pub struct PersonFollowerForm {
|
||||
#[cfg_attr(feature = "full", diesel(column_name = target_id))]
|
||||
pub person_id: PersonId,
|
||||
#[cfg_attr(feature = "full", diesel(column_name = person_id))]
|
||||
pub follower_id: PersonId,
|
||||
#[cfg_attr(feature = "full", diesel(column_name = follow_pending))]
|
||||
pub pending: bool,
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
use crate::newtypes::PersonId;
|
||||
#[cfg(feature = "full")]
|
||||
use crate::schema::person_block;
|
||||
use crate::schema::person_actions;
|
||||
use chrono::{DateTime, Utc};
|
||||
#[cfg(feature = "full")]
|
||||
use diesel::{dsl, expression_methods::NullableExpressionMethods};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
|
||||
|
@ -10,17 +12,19 @@ use serde::{Deserialize, Serialize};
|
|||
derive(Queryable, Selectable, Associations, Identifiable)
|
||||
)]
|
||||
#[cfg_attr(feature = "full", diesel(belongs_to(crate::source::person::Person)))]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = person_block))]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = person_actions))]
|
||||
#[cfg_attr(feature = "full", diesel(primary_key(person_id, target_id)))]
|
||||
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
|
||||
pub struct PersonBlock {
|
||||
pub person_id: PersonId,
|
||||
pub target_id: PersonId,
|
||||
#[cfg_attr(feature = "full", diesel(select_expression = person_actions::blocked.assume_not_null()))]
|
||||
#[cfg_attr(feature = "full", diesel(select_expression_type = dsl::AssumeNotNull<person_actions::blocked>))]
|
||||
pub published: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = person_block))]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = person_actions))]
|
||||
pub struct PersonBlockForm {
|
||||
pub person_id: PersonId,
|
||||
pub target_id: PersonId,
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
use crate::newtypes::{CommunityId, DbUrl, LanguageId, PersonId, PostId};
|
||||
#[cfg(feature = "full")]
|
||||
use crate::schema::{post, post_hide, post_like, post_read, post_saved};
|
||||
use crate::schema::{post, post_actions};
|
||||
use chrono::{DateTime, Utc};
|
||||
#[cfg(feature = "full")]
|
||||
use diesel::{dsl, expression_methods::NullableExpressionMethods};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::skip_serializing_none;
|
||||
#[cfg(feature = "full")]
|
||||
|
@ -149,22 +151,27 @@ pub struct PostUpdateForm {
|
|||
derive(Identifiable, Queryable, Selectable, Associations)
|
||||
)]
|
||||
#[cfg_attr(feature = "full", diesel(belongs_to(crate::source::post::Post)))]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = post_like))]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = post_actions))]
|
||||
#[cfg_attr(feature = "full", diesel(primary_key(person_id, post_id)))]
|
||||
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
|
||||
pub struct PostLike {
|
||||
pub post_id: PostId,
|
||||
pub person_id: PersonId,
|
||||
#[cfg_attr(feature = "full", diesel(select_expression = post_actions::like_score.assume_not_null()))]
|
||||
#[cfg_attr(feature = "full", diesel(select_expression_type = dsl::AssumeNotNull<post_actions::like_score>))]
|
||||
pub score: i16,
|
||||
#[cfg_attr(feature = "full", diesel(select_expression = post_actions::liked.assume_not_null()))]
|
||||
#[cfg_attr(feature = "full", diesel(select_expression_type = dsl::AssumeNotNull<post_actions::liked>))]
|
||||
pub published: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = post_like))]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = post_actions))]
|
||||
pub struct PostLikeForm {
|
||||
pub post_id: PostId,
|
||||
pub person_id: PersonId,
|
||||
#[cfg_attr(feature = "full", diesel(column_name = like_score))]
|
||||
pub score: i16,
|
||||
}
|
||||
|
||||
|
@ -174,17 +181,19 @@ pub struct PostLikeForm {
|
|||
derive(Identifiable, Queryable, Selectable, Associations)
|
||||
)]
|
||||
#[cfg_attr(feature = "full", diesel(belongs_to(crate::source::post::Post)))]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = post_saved))]
|
||||
#[cfg_attr(feature = "full", diesel(primary_key(post_id, person_id)))]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = post_actions))]
|
||||
#[cfg_attr(feature = "full", diesel(primary_key(person_id, post_id)))]
|
||||
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
|
||||
pub struct PostSaved {
|
||||
pub post_id: PostId,
|
||||
pub person_id: PersonId,
|
||||
#[cfg_attr(feature = "full", diesel(select_expression = post_actions::saved.assume_not_null()))]
|
||||
#[cfg_attr(feature = "full", diesel(select_expression_type = dsl::AssumeNotNull<post_actions::saved>))]
|
||||
pub published: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = post_saved))]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = post_actions))]
|
||||
pub struct PostSavedForm {
|
||||
pub post_id: PostId,
|
||||
pub person_id: PersonId,
|
||||
|
@ -196,17 +205,19 @@ pub struct PostSavedForm {
|
|||
derive(Identifiable, Queryable, Selectable, Associations)
|
||||
)]
|
||||
#[cfg_attr(feature = "full", diesel(belongs_to(crate::source::post::Post)))]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = post_read))]
|
||||
#[cfg_attr(feature = "full", diesel(primary_key(post_id, person_id)))]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = post_actions))]
|
||||
#[cfg_attr(feature = "full", diesel(primary_key(person_id, post_id)))]
|
||||
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
|
||||
pub struct PostRead {
|
||||
pub post_id: PostId,
|
||||
pub person_id: PersonId,
|
||||
#[cfg_attr(feature = "full", diesel(select_expression = post_actions::read.assume_not_null()))]
|
||||
#[cfg_attr(feature = "full", diesel(select_expression_type = dsl::AssumeNotNull<post_actions::read>))]
|
||||
pub published: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = post_read))]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = post_actions))]
|
||||
pub(crate) struct PostReadForm {
|
||||
pub post_id: PostId,
|
||||
pub person_id: PersonId,
|
||||
|
@ -218,17 +229,19 @@ pub(crate) struct PostReadForm {
|
|||
derive(Identifiable, Queryable, Selectable, Associations)
|
||||
)]
|
||||
#[cfg_attr(feature = "full", diesel(belongs_to(crate::source::post::Post)))]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = post_hide))]
|
||||
#[cfg_attr(feature = "full", diesel(primary_key(post_id, person_id)))]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = post_actions))]
|
||||
#[cfg_attr(feature = "full", diesel(primary_key(person_id, post_id)))]
|
||||
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
|
||||
pub struct PostHide {
|
||||
pub post_id: PostId,
|
||||
pub person_id: PersonId,
|
||||
#[cfg_attr(feature = "full", diesel(select_expression = post_actions::hidden.assume_not_null()))]
|
||||
#[cfg_attr(feature = "full", diesel(select_expression_type = dsl::AssumeNotNull<post_actions::hidden>))]
|
||||
pub published: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = post_hide))]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = post_actions))]
|
||||
pub(crate) struct PostHideForm {
|
||||
pub post_id: PostId,
|
||||
pub person_id: PersonId,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::{
|
||||
newtypes::{CommunityId, DbUrl, PersonId},
|
||||
utils::{get_conn, DbPool},
|
||||
utils::{get_conn, uplete, DbPool},
|
||||
};
|
||||
use diesel::{
|
||||
associations::HasTable,
|
||||
|
@ -76,7 +76,7 @@ pub trait Followable {
|
|||
) -> Result<Self, Error>
|
||||
where
|
||||
Self: Sized;
|
||||
async fn unfollow(pool: &mut DbPool<'_>, form: &Self::Form) -> Result<usize, Error>
|
||||
async fn unfollow(pool: &mut DbPool<'_>, form: &Self::Form) -> Result<uplete::Count, Error>
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
@ -87,7 +87,7 @@ pub trait Joinable {
|
|||
async fn join(pool: &mut DbPool<'_>, form: &Self::Form) -> Result<Self, Error>
|
||||
where
|
||||
Self: Sized;
|
||||
async fn leave(pool: &mut DbPool<'_>, form: &Self::Form) -> Result<usize, Error>
|
||||
async fn leave(pool: &mut DbPool<'_>, form: &Self::Form) -> Result<uplete::Count, Error>
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
@ -103,7 +103,7 @@ pub trait Likeable {
|
|||
pool: &mut DbPool<'_>,
|
||||
person_id: PersonId,
|
||||
item_id: Self::IdType,
|
||||
) -> Result<usize, Error>
|
||||
) -> Result<uplete::Count, Error>
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
@ -114,7 +114,7 @@ pub trait Bannable {
|
|||
async fn ban(pool: &mut DbPool<'_>, form: &Self::Form) -> Result<Self, Error>
|
||||
where
|
||||
Self: Sized;
|
||||
async fn unban(pool: &mut DbPool<'_>, form: &Self::Form) -> Result<usize, Error>
|
||||
async fn unban(pool: &mut DbPool<'_>, form: &Self::Form) -> Result<uplete::Count, Error>
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
@ -125,7 +125,7 @@ pub trait Saveable {
|
|||
async fn save(pool: &mut DbPool<'_>, form: &Self::Form) -> Result<Self, Error>
|
||||
where
|
||||
Self: Sized;
|
||||
async fn unsave(pool: &mut DbPool<'_>, form: &Self::Form) -> Result<usize, Error>
|
||||
async fn unsave(pool: &mut DbPool<'_>, form: &Self::Form) -> Result<uplete::Count, Error>
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
@ -136,7 +136,7 @@ pub trait Blockable {
|
|||
async fn block(pool: &mut DbPool<'_>, form: &Self::Form) -> Result<Self, Error>
|
||||
where
|
||||
Self: Sized;
|
||||
async fn unblock(pool: &mut DbPool<'_>, form: &Self::Form) -> Result<usize, Error>
|
||||
async fn unblock(pool: &mut DbPool<'_>, form: &Self::Form) -> Result<uplete::Count, Error>
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
|
|
@ -1,18 +1,30 @@
|
|||
pub mod uplete;
|
||||
|
||||
use crate::{newtypes::DbUrl, CommentSortType, PostSortType};
|
||||
use chrono::{DateTime, TimeDelta, Utc};
|
||||
use deadpool::Runtime;
|
||||
use diesel::{
|
||||
dsl,
|
||||
expression::AsExpression,
|
||||
helper_types::AsExprOf,
|
||||
pg::Pg,
|
||||
query_builder::{Query, QueryFragment},
|
||||
query_dsl::methods::LimitDsl,
|
||||
query_dsl::methods::{FilterDsl, FindDsl, LimitDsl},
|
||||
query_source::{Alias, AliasSource, AliasedField},
|
||||
result::{
|
||||
ConnectionError,
|
||||
ConnectionResult,
|
||||
Error::{self as DieselError, QueryBuilderError},
|
||||
},
|
||||
sql_types::{self, Timestamptz},
|
||||
sql_types::{self, SingleValue, Timestamptz},
|
||||
Column,
|
||||
Expression,
|
||||
ExpressionMethods,
|
||||
IntoSql,
|
||||
JoinOnDsl,
|
||||
NullableExpressionMethods,
|
||||
QuerySource,
|
||||
Table,
|
||||
};
|
||||
use diesel_async::{
|
||||
pg::AsyncPgConnection,
|
||||
|
@ -23,6 +35,7 @@ use diesel_async::{
|
|||
},
|
||||
AsyncConnection,
|
||||
};
|
||||
use diesel_bind_if_some::BindIfSome;
|
||||
use futures_util::{future::BoxFuture, Future, FutureExt};
|
||||
use i_love_jesus::CursorKey;
|
||||
use lemmy_utils::{
|
||||
|
@ -540,6 +553,117 @@ pub fn now() -> AsExprOf<diesel::dsl::now, diesel::sql_types::Timestamptz> {
|
|||
diesel::dsl::now.into_sql::<Timestamptz>()
|
||||
}
|
||||
|
||||
/// Trait alias for a type that can be converted to an SQL tuple using `IntoSql::into_sql`
|
||||
pub trait AsRecord: Expression + AsExpression<sql_types::Record<Self::SqlType>>
|
||||
where
|
||||
Self::SqlType: 'static,
|
||||
{
|
||||
}
|
||||
|
||||
impl<T: Expression + AsExpression<sql_types::Record<T::SqlType>>> AsRecord for T where
|
||||
T::SqlType: 'static
|
||||
{
|
||||
}
|
||||
|
||||
/// Output of `IntoSql::into_sql` for a type that implements `AsRecord`
|
||||
pub type AsRecordOutput<T> = dsl::AsExprOf<T, sql_types::Record<<T as Expression>::SqlType>>;
|
||||
|
||||
/// Output of `t.on((l0, l1).into_sql().eq((r0, r1)))`
|
||||
type OnTupleEq<T, L0, L1, R0, R1> = dsl::On<T, dsl::Eq<AsRecordOutput<(L0, L1)>, (R0, R1)>>;
|
||||
|
||||
/// Creates an `ON` clause for a table where a person ID and another column are used as the
|
||||
/// primary key. Use with the `QueryDsl::left_join` method.
|
||||
///
|
||||
/// This example modifies a query to make columns in `community_actions` available:
|
||||
///
|
||||
/// ```
|
||||
/// community::table
|
||||
/// .left_join(actions(
|
||||
/// community_actions::table,
|
||||
/// my_person_id,
|
||||
/// community::id,
|
||||
/// ))
|
||||
/// ```
|
||||
pub fn actions<T, P, C, K0, K1>(
|
||||
actions_table: T,
|
||||
person_id: Option<P>,
|
||||
target_id: C,
|
||||
) -> OnTupleEq<T, dsl::Nullable<K0>, K1, BindIfSome<dsl::AsExprOf<P, sql_types::Integer>>, C>
|
||||
where
|
||||
T: Table<PrimaryKey = (K0, K1)> + Copy,
|
||||
K0: Expression,
|
||||
P: AsExpression<sql_types::Integer>,
|
||||
(dsl::Nullable<K0>, K1): AsRecord,
|
||||
(BindIfSome<dsl::AsExprOf<P, sql_types::Integer>>, C):
|
||||
AsExpression<<AsRecordOutput<(dsl::Nullable<K0>, K1)> as Expression>::SqlType>,
|
||||
{
|
||||
let (k0, k1) = actions_table.primary_key();
|
||||
actions_table.on((k0.nullable(), k1).into_sql().eq((
|
||||
BindIfSome(person_id.map(diesel::IntoSql::into_sql)),
|
||||
target_id,
|
||||
)))
|
||||
}
|
||||
|
||||
/// Like `actions` but `actions_table` is an alias and person id is not nullable
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub fn actions_alias<T, P, C, K0, K1>(
|
||||
actions_table: Alias<T>,
|
||||
person_id: P,
|
||||
target_id: C,
|
||||
) -> OnTupleEq<Alias<T>, AliasedField<T, K0>, AliasedField<T, K1>, P, C>
|
||||
where
|
||||
Alias<T>: QuerySource + Copy,
|
||||
T: AliasSource<Target: Table<PrimaryKey = (K0, K1)>> + Default,
|
||||
K0: Column<Table = T::Target>,
|
||||
K1: Column<Table = T::Target>,
|
||||
(AliasedField<T, K0>, AliasedField<T, K1>): AsRecord,
|
||||
(P, C): AsExpression<
|
||||
<AsRecordOutput<(AliasedField<T, K0>, AliasedField<T, K1>)> as Expression>::SqlType,
|
||||
>,
|
||||
{
|
||||
let (k0, k1) = T::default().target().primary_key();
|
||||
actions_table.on(
|
||||
(actions_table.field(k0), actions_table.field(k1))
|
||||
.into_sql()
|
||||
.eq((person_id, target_id)),
|
||||
)
|
||||
}
|
||||
|
||||
/// `action_query(table_name::action_name)` is the same as
|
||||
/// `table_name::table.filter(table_name::action_name.is_not_null())`.
|
||||
pub fn action_query<C>(column: C) -> dsl::Filter<C::Table, dsl::IsNotNull<C>>
|
||||
where
|
||||
C: Column<Table: Default + FilterDsl<dsl::IsNotNull<C>>, SqlType: SingleValue>,
|
||||
{
|
||||
action_query_with_fn(column, |t| t)
|
||||
}
|
||||
|
||||
/// `find_action(table_name::action_name, key)` is the same as
|
||||
/// `table_name::table.find(key).filter(table_name::action_name.is_not_null())`.
|
||||
pub fn find_action<C, K>(
|
||||
column: C,
|
||||
key: K,
|
||||
) -> dsl::Filter<dsl::Find<C::Table, K>, dsl::IsNotNull<C>>
|
||||
where
|
||||
C:
|
||||
Column<Table: Default + FindDsl<K, Output: FilterDsl<dsl::IsNotNull<C>>>, SqlType: SingleValue>,
|
||||
{
|
||||
action_query_with_fn(column, |t| t.find(key))
|
||||
}
|
||||
|
||||
/// `action_query_with_fn(table_name::action_name, f)` is the same as
|
||||
/// `f(table_name::table).filter(table_name::action_name.is_not_null())`.
|
||||
fn action_query_with_fn<C, Q>(
|
||||
column: C,
|
||||
f: impl FnOnce(C::Table) -> Q,
|
||||
) -> dsl::Filter<Q, dsl::IsNotNull<C>>
|
||||
where
|
||||
C: Column<Table: Default, SqlType: SingleValue>,
|
||||
Q: FilterDsl<dsl::IsNotNull<C>>,
|
||||
{
|
||||
f(C::Table::default()).filter(column.is_not_null())
|
||||
}
|
||||
|
||||
pub type ResultFuture<'a, T> = BoxFuture<'a, Result<T, DieselError>>;
|
||||
|
||||
pub trait ReadFn<'a, T, Args>: Fn(DbConn<'a>, Args) -> ResultFuture<'a, T> {}
|
||||
|
|
423
crates/db_schema/src/utils/uplete.rs
Normal file
423
crates/db_schema/src/utils/uplete.rs
Normal file
|
@ -0,0 +1,423 @@
|
|||
use diesel::{
|
||||
associations::HasTable,
|
||||
dsl,
|
||||
expression::{is_aggregate, ValidGrouping},
|
||||
pg::Pg,
|
||||
query_builder::{AsQuery, AstPass, Query, QueryFragment, QueryId},
|
||||
query_dsl::methods::{FilterDsl, SelectDsl},
|
||||
result::Error,
|
||||
sql_types,
|
||||
Column,
|
||||
Expression,
|
||||
Table,
|
||||
};
|
||||
use std::any::TypeId;
|
||||
use tuplex::IntoArray;
|
||||
|
||||
/// Set columns (each specified with `UpleteBuilder::set_null`) to null in the rows found by
|
||||
/// `query`, and delete rows that have no remaining non-null values outside of the primary key
|
||||
pub fn new<Q>(query: Q) -> UpleteBuilder<dsl::Select<Q::Query, <Q::Table as Table>::PrimaryKey>>
|
||||
where
|
||||
Q: AsQuery + HasTable,
|
||||
Q::Table: Default,
|
||||
Q::Query: SelectDsl<<Q::Table as Table>::PrimaryKey>,
|
||||
|
||||
// For better error messages
|
||||
UpleteBuilder<Q>: AsQuery,
|
||||
{
|
||||
UpleteBuilder {
|
||||
query: query.as_query().select(Q::Table::default().primary_key()),
|
||||
set_null_columns: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub struct UpleteBuilder<Q> {
|
||||
query: Q,
|
||||
set_null_columns: Vec<DynColumn>,
|
||||
}
|
||||
|
||||
impl<Q: HasTable> UpleteBuilder<Q> {
|
||||
pub fn set_null<C: Column<Table = Q::Table> + Into<DynColumn>>(mut self, column: C) -> Self {
|
||||
self.set_null_columns.push(column.into());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<Q> AsQuery for UpleteBuilder<Q>
|
||||
where
|
||||
Q: HasTable,
|
||||
Q::Table: Default + QueryFragment<Pg> + Send + 'static,
|
||||
<Q::Table as Table>::PrimaryKey: IntoArray<DynColumn> + QueryFragment<Pg> + Send + 'static,
|
||||
<Q::Table as Table>::AllColumns: IntoArray<DynColumn>,
|
||||
<<Q::Table as Table>::PrimaryKey as IntoArray<DynColumn>>::Output: IntoIterator<Item = DynColumn>,
|
||||
<<Q::Table as Table>::AllColumns as IntoArray<DynColumn>>::Output: IntoIterator<Item = DynColumn>,
|
||||
Q: Clone + FilterDsl<AllNull> + FilterDsl<dsl::not<AllNull>>,
|
||||
dsl::Filter<Q, AllNull>: QueryFragment<Pg> + Send + 'static,
|
||||
dsl::Filter<Q, dsl::not<AllNull>>: QueryFragment<Pg> + Send + 'static,
|
||||
{
|
||||
type Query = UpleteQuery;
|
||||
|
||||
type SqlType = (sql_types::BigInt, sql_types::BigInt);
|
||||
|
||||
fn as_query(self) -> Self::Query {
|
||||
let table = Q::Table::default;
|
||||
let deletion_condition = AllNull(
|
||||
Q::Table::all_columns()
|
||||
.into_array()
|
||||
.into_iter()
|
||||
.filter(|c: &DynColumn| {
|
||||
table()
|
||||
.primary_key()
|
||||
.into_array()
|
||||
.into_iter()
|
||||
.chain(self.set_null_columns.iter().cloned())
|
||||
.all(|excluded_column| excluded_column.type_id != c.type_id)
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
UpleteQuery {
|
||||
// Updated rows and deleted rows must not overlap, so updating all rows and using the returned
|
||||
// new rows to determine which ones to delete is not an option.
|
||||
//
|
||||
// https://www.postgresql.org/docs/16/queries-with.html#QUERIES-WITH-MODIFYING
|
||||
//
|
||||
// "Trying to update the same row twice in a single statement is not supported. Only one of
|
||||
// the modifications takes place, but it is not easy (and sometimes not possible) to reliably
|
||||
// predict which one. This also applies to deleting a row that was already updated in the same
|
||||
// statement: only the update is performed."
|
||||
update_subquery: Box::new(
|
||||
self
|
||||
.query
|
||||
.clone()
|
||||
.filter(dsl::not(deletion_condition.clone())),
|
||||
),
|
||||
delete_subquery: Box::new(self.query.filter(deletion_condition)),
|
||||
table: Box::new(table()),
|
||||
primary_key: Box::new(table().primary_key()),
|
||||
set_null_columns: self.set_null_columns,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct UpleteQuery {
|
||||
update_subquery: Box<dyn QueryFragment<Pg> + Send + 'static>,
|
||||
delete_subquery: Box<dyn QueryFragment<Pg> + Send + 'static>,
|
||||
table: Box<dyn QueryFragment<Pg> + Send + 'static>,
|
||||
primary_key: Box<dyn QueryFragment<Pg> + Send + 'static>,
|
||||
set_null_columns: Vec<DynColumn>,
|
||||
}
|
||||
|
||||
impl QueryId for UpleteQuery {
|
||||
type QueryId = ();
|
||||
|
||||
const HAS_STATIC_QUERY_ID: bool = false;
|
||||
}
|
||||
|
||||
impl Query for UpleteQuery {
|
||||
type SqlType = (sql_types::BigInt, sql_types::BigInt);
|
||||
}
|
||||
|
||||
impl QueryFragment<Pg> for UpleteQuery {
|
||||
fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, Pg>) -> Result<(), Error> {
|
||||
assert_ne!(self.set_null_columns.len(), 0, "`set_null` was not called");
|
||||
|
||||
// Declare `update_keys` and `delete_keys` CTEs, which select primary keys
|
||||
for (prefix, subquery) in [
|
||||
("WITH update_keys", &self.update_subquery),
|
||||
(", delete_keys", &self.delete_subquery),
|
||||
] {
|
||||
out.push_sql(prefix);
|
||||
out.push_sql(" AS (");
|
||||
subquery.walk_ast(out.reborrow())?;
|
||||
out.push_sql(" FOR UPDATE)");
|
||||
}
|
||||
|
||||
// Update rows that are referenced in `update_keys`
|
||||
out.push_sql(", update_result AS (UPDATE ");
|
||||
self.table.walk_ast(out.reborrow())?;
|
||||
let mut item_prefix = " SET ";
|
||||
for column in &self.set_null_columns {
|
||||
out.push_sql(item_prefix);
|
||||
out.push_identifier(column.name)?;
|
||||
out.push_sql(" = NULL");
|
||||
item_prefix = ",";
|
||||
}
|
||||
out.push_sql(" WHERE (");
|
||||
self.primary_key.walk_ast(out.reborrow())?;
|
||||
out.push_sql(") = ANY (SELECT * FROM update_keys) RETURNING 1)");
|
||||
|
||||
// Delete rows that are referenced in `delete_keys`
|
||||
out.push_sql(", delete_result AS (DELETE FROM ");
|
||||
self.table.walk_ast(out.reborrow())?;
|
||||
out.push_sql(" WHERE (");
|
||||
self.primary_key.walk_ast(out.reborrow())?;
|
||||
out.push_sql(") = ANY (SELECT * FROM delete_keys) RETURNING 1)");
|
||||
|
||||
// Count updated rows and deleted rows (`RETURNING 1` makes this possible)
|
||||
out.push_sql(" SELECT (SELECT count(*) FROM update_result)");
|
||||
out.push_sql(", (SELECT count(*) FROM delete_result)");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// Types other than `DynColumn` are only used in tests
|
||||
#[derive(Clone)]
|
||||
pub struct AllNull<T = DynColumn>(Vec<T>);
|
||||
|
||||
impl<T> Expression for AllNull<T> {
|
||||
type SqlType = sql_types::Bool;
|
||||
}
|
||||
|
||||
impl<T> ValidGrouping<()> for AllNull<T> {
|
||||
type IsAggregate = is_aggregate::No;
|
||||
}
|
||||
|
||||
impl<T: QueryFragment<Pg>> QueryFragment<Pg> for AllNull<T> {
|
||||
fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, Pg>) -> Result<(), Error> {
|
||||
// Must produce a valid expression even if `self.0` is empty
|
||||
out.push_sql("(TRUE");
|
||||
for item in &self.0 {
|
||||
out.push_sql(" AND (");
|
||||
item.walk_ast(out.reborrow())?;
|
||||
out.push_sql(" IS NULL)");
|
||||
}
|
||||
out.push_sql(")");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DynColumn {
|
||||
type_id: TypeId,
|
||||
name: &'static str,
|
||||
}
|
||||
|
||||
impl<T: Column + 'static> From<T> for DynColumn {
|
||||
fn from(_value: T) -> Self {
|
||||
DynColumn {
|
||||
type_id: TypeId::of::<T>(),
|
||||
name: T::NAME,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl QueryFragment<Pg> for DynColumn {
|
||||
fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, Pg>) -> Result<(), Error> {
|
||||
out.push_identifier(self.name)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Queryable, PartialEq, Eq, Debug)]
|
||||
pub struct Count {
|
||||
pub updated: i64,
|
||||
pub deleted: i64,
|
||||
}
|
||||
|
||||
impl Count {
|
||||
pub fn only_updated(n: i64) -> Self {
|
||||
Count {
|
||||
updated: n,
|
||||
deleted: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn only_deleted(n: i64) -> Self {
|
||||
Count {
|
||||
updated: 0,
|
||||
deleted: n,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::AllNull;
|
||||
use crate::utils::{build_db_pool_for_tests, get_conn, DbConn};
|
||||
use diesel::{
|
||||
debug_query,
|
||||
insert_into,
|
||||
pg::Pg,
|
||||
query_builder::{AsQuery, QueryId},
|
||||
select,
|
||||
sql_types,
|
||||
AppearsOnTable,
|
||||
ExpressionMethods,
|
||||
IntoSql,
|
||||
QueryDsl,
|
||||
SelectableExpression,
|
||||
};
|
||||
use diesel_async::{RunQueryDsl, SimpleAsyncConnection};
|
||||
use lemmy_utils::error::LemmyResult;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serial_test::serial;
|
||||
|
||||
impl<T, QS> AppearsOnTable<QS> for AllNull<T> {}
|
||||
|
||||
impl<T, QS> SelectableExpression<QS> for AllNull<T> {}
|
||||
|
||||
impl<T> QueryId for AllNull<T> {
|
||||
type QueryId = ();
|
||||
const HAS_STATIC_QUERY_ID: bool = false;
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
t (id1, id2) {
|
||||
// uplete doesn't work for non-tuple primary key
|
||||
id1 -> Int4,
|
||||
id2 -> Int4,
|
||||
a -> Nullable<Int4>,
|
||||
b -> Nullable<Int4>,
|
||||
}
|
||||
}
|
||||
|
||||
async fn expect_rows(
|
||||
conn: &mut DbConn<'_>,
|
||||
expected: &[(Option<i32>, Option<i32>)],
|
||||
) -> LemmyResult<()> {
|
||||
let rows: Vec<(Option<i32>, Option<i32>)> = t::table
|
||||
.select((t::a, t::b))
|
||||
.order_by(t::id1)
|
||||
.load(conn)
|
||||
.await?;
|
||||
assert_eq!(expected, &rows);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Main purpose of this test is to check accuracy of the returned `Count`, which other modules'
|
||||
// tests rely on
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_count() -> LemmyResult<()> {
|
||||
let pool = &build_db_pool_for_tests();
|
||||
let pool = &mut pool.into();
|
||||
let mut conn = get_conn(pool).await?;
|
||||
|
||||
conn
|
||||
.batch_execute("CREATE TABLE t (id1 serial, id2 int NOT NULL DEFAULT 1, a int, b int, PRIMARY KEY (id1, id2));")
|
||||
.await?;
|
||||
expect_rows(&mut conn, &[]).await?;
|
||||
|
||||
insert_into(t::table)
|
||||
.values(&[
|
||||
(t::a.eq(Some(1)), t::b.eq(Some(2))),
|
||||
(t::a.eq(Some(3)), t::b.eq(None)),
|
||||
(t::a.eq(Some(4)), t::b.eq(Some(5))),
|
||||
])
|
||||
.execute(&mut conn)
|
||||
.await?;
|
||||
expect_rows(
|
||||
&mut conn,
|
||||
&[(Some(1), Some(2)), (Some(3), None), (Some(4), Some(5))],
|
||||
)
|
||||
.await?;
|
||||
|
||||
let count1 = super::new(t::table)
|
||||
.set_null(t::a)
|
||||
.get_result(&mut conn)
|
||||
.await?;
|
||||
assert_eq!(
|
||||
super::Count {
|
||||
updated: 2,
|
||||
deleted: 1
|
||||
},
|
||||
count1
|
||||
);
|
||||
expect_rows(&mut conn, &[(None, Some(2)), (None, Some(5))]).await?;
|
||||
|
||||
let count2 = super::new(t::table)
|
||||
.set_null(t::b)
|
||||
.get_result(&mut conn)
|
||||
.await?;
|
||||
assert_eq!(super::Count::only_deleted(2), count2);
|
||||
expect_rows(&mut conn, &[]).await?;
|
||||
|
||||
conn.batch_execute("DROP TABLE t;").await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn expected_sql(check_null: &str, set_null: &str) -> String {
|
||||
let with_queries = {
|
||||
let key = r#""t"."id1", "t"."id2""#;
|
||||
let t = r#""t""#;
|
||||
|
||||
let update_keys = format!("SELECT {key} FROM {t} WHERE NOT (({check_null})) FOR UPDATE");
|
||||
let delete_keys = format!("SELECT {key} FROM {t} WHERE ({check_null}) FOR UPDATE");
|
||||
let update_result = format!(
|
||||
"UPDATE {t} SET {set_null} WHERE ({key}) = ANY (SELECT * FROM update_keys) RETURNING 1"
|
||||
);
|
||||
let delete_result =
|
||||
format!("DELETE FROM {t} WHERE ({key}) = ANY (SELECT * FROM delete_keys) RETURNING 1");
|
||||
|
||||
format!("update_keys AS ({update_keys}), delete_keys AS ({delete_keys}), update_result AS ({update_result}), delete_result AS ({delete_result})")
|
||||
};
|
||||
let update_count = "SELECT count(*) FROM update_result";
|
||||
let delete_count = "SELECT count(*) FROM delete_result";
|
||||
|
||||
format!(r#"WITH {with_queries} SELECT ({update_count}), ({delete_count}) -- binds: []"#)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_generated_sql() {
|
||||
// Unlike the `get_result` method, `debug_query` does not automatically call `as_query`
|
||||
assert_eq!(
|
||||
debug_query::<Pg, _>(&super::new(t::table).set_null(t::b).as_query()).to_string(),
|
||||
expected_sql(r#"TRUE AND ("a" IS NULL)"#, r#""b" = NULL"#)
|
||||
);
|
||||
assert_eq!(
|
||||
debug_query::<Pg, _>(
|
||||
&super::new(t::table)
|
||||
.set_null(t::a)
|
||||
.set_null(t::b)
|
||||
.as_query()
|
||||
)
|
||||
.to_string(),
|
||||
expected_sql(r#"TRUE"#, r#""a" = NULL,"b" = NULL"#)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_count_methods() {
|
||||
assert_eq!(
|
||||
super::Count::only_updated(1),
|
||||
super::Count {
|
||||
updated: 1,
|
||||
deleted: 0
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
super::Count::only_deleted(1),
|
||||
super::Count {
|
||||
updated: 0,
|
||||
deleted: 1
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_all_null() -> LemmyResult<()> {
|
||||
let pool = &build_db_pool_for_tests();
|
||||
let pool = &mut pool.into();
|
||||
let mut conn = get_conn(pool).await?;
|
||||
|
||||
let some = Some(1).into_sql::<sql_types::Nullable<sql_types::Integer>>();
|
||||
let none = None::<i32>.into_sql::<sql_types::Nullable<sql_types::Integer>>();
|
||||
|
||||
// Allows type inference for `vec![]`
|
||||
let mut all_null = |items| select(AllNull(items)).get_result::<bool>(&mut conn);
|
||||
|
||||
assert!(all_null(vec![]).await?);
|
||||
assert!(all_null(vec![none]).await?);
|
||||
assert!(all_null(vec![none, none]).await?);
|
||||
assert!(all_null(vec![none, none, none]).await?);
|
||||
assert!(!all_null(vec![some]).await?);
|
||||
assert!(!all_null(vec![some, none]).await?);
|
||||
assert!(!all_null(vec![none, some, none]).await?);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -11,25 +11,33 @@ use diesel::{
|
|||
};
|
||||
use diesel_async::RunQueryDsl;
|
||||
use lemmy_db_schema::{
|
||||
aliases,
|
||||
aliases::{self, creator_community_actions},
|
||||
newtypes::{CommentId, CommentReportId, CommunityId, PersonId},
|
||||
schema::{
|
||||
comment,
|
||||
comment_actions,
|
||||
comment_aggregates,
|
||||
comment_like,
|
||||
comment_report,
|
||||
comment_saved,
|
||||
community,
|
||||
community_follower,
|
||||
community_moderator,
|
||||
community_person_ban,
|
||||
community_actions,
|
||||
local_user,
|
||||
person,
|
||||
person_block,
|
||||
person_actions,
|
||||
post,
|
||||
},
|
||||
source::community::CommunityFollower,
|
||||
utils::{get_conn, limit_and_offset, DbConn, DbPool, ListFn, Queries, ReadFn},
|
||||
utils::{
|
||||
actions,
|
||||
actions_alias,
|
||||
functions::coalesce,
|
||||
get_conn,
|
||||
limit_and_offset,
|
||||
DbConn,
|
||||
DbPool,
|
||||
ListFn,
|
||||
Queries,
|
||||
ReadFn,
|
||||
},
|
||||
};
|
||||
|
||||
fn queries<'a>() -> Queries<
|
||||
|
@ -46,40 +54,20 @@ fn queries<'a>() -> Queries<
|
|||
.inner_join(
|
||||
comment_aggregates::table.on(comment_report::comment_id.eq(comment_aggregates::comment_id)),
|
||||
)
|
||||
.left_join(
|
||||
comment_like::table.on(
|
||||
comment::id
|
||||
.eq(comment_like::comment_id)
|
||||
.and(comment_like::person_id.eq(my_person_id)),
|
||||
),
|
||||
)
|
||||
.left_join(actions(
|
||||
comment_actions::table,
|
||||
Some(my_person_id),
|
||||
comment_report::comment_id,
|
||||
))
|
||||
.left_join(
|
||||
aliases::person2
|
||||
.on(comment_report::resolver_id.eq(aliases::person2.field(person::id).nullable())),
|
||||
)
|
||||
.left_join(
|
||||
community_person_ban::table.on(
|
||||
community::id
|
||||
.eq(community_person_ban::community_id)
|
||||
.and(community_person_ban::person_id.eq(comment::creator_id))
|
||||
.and(
|
||||
community_person_ban::expires
|
||||
.is_null()
|
||||
.or(community_person_ban::expires.gt(now)),
|
||||
),
|
||||
),
|
||||
)
|
||||
.left_join(
|
||||
aliases::community_moderator1.on(
|
||||
community::id
|
||||
.eq(aliases::community_moderator1.field(community_moderator::community_id))
|
||||
.and(
|
||||
aliases::community_moderator1
|
||||
.field(community_moderator::person_id)
|
||||
.eq(comment::creator_id),
|
||||
),
|
||||
),
|
||||
)
|
||||
.left_join(actions_alias(
|
||||
creator_community_actions,
|
||||
comment::creator_id,
|
||||
post::community_id,
|
||||
))
|
||||
.left_join(
|
||||
local_user::table.on(
|
||||
comment::creator_id
|
||||
|
@ -87,27 +75,16 @@ fn queries<'a>() -> Queries<
|
|||
.and(local_user::admin.eq(true)),
|
||||
),
|
||||
)
|
||||
.left_join(
|
||||
person_block::table.on(
|
||||
comment::creator_id
|
||||
.eq(person_block::target_id)
|
||||
.and(person_block::person_id.eq(my_person_id)),
|
||||
),
|
||||
)
|
||||
.left_join(
|
||||
community_follower::table.on(
|
||||
post::community_id
|
||||
.eq(community_follower::community_id)
|
||||
.and(community_follower::person_id.eq(my_person_id)),
|
||||
),
|
||||
)
|
||||
.left_join(
|
||||
comment_saved::table.on(
|
||||
comment::id
|
||||
.eq(comment_saved::comment_id)
|
||||
.and(comment_saved::person_id.eq(my_person_id)),
|
||||
),
|
||||
)
|
||||
.left_join(actions(
|
||||
person_actions::table,
|
||||
Some(my_person_id),
|
||||
comment::creator_id,
|
||||
))
|
||||
.left_join(actions(
|
||||
community_actions::table,
|
||||
Some(my_person_id),
|
||||
post::community_id,
|
||||
))
|
||||
.select((
|
||||
comment_report::all_columns,
|
||||
comment::all_columns,
|
||||
|
@ -116,16 +93,28 @@ fn queries<'a>() -> Queries<
|
|||
person::all_columns,
|
||||
aliases::person1.fields(person::all_columns),
|
||||
comment_aggregates::all_columns,
|
||||
community_person_ban::community_id.nullable().is_not_null(),
|
||||
aliases::community_moderator1
|
||||
.field(community_moderator::community_id)
|
||||
coalesce(
|
||||
creator_community_actions
|
||||
.field(community_actions::received_ban)
|
||||
.nullable()
|
||||
.is_not_null()
|
||||
.or(
|
||||
creator_community_actions
|
||||
.field(community_actions::ban_expires)
|
||||
.nullable()
|
||||
.gt(now),
|
||||
),
|
||||
false,
|
||||
),
|
||||
creator_community_actions
|
||||
.field(community_actions::became_moderator)
|
||||
.nullable()
|
||||
.is_not_null(),
|
||||
local_user::admin.nullable().is_not_null(),
|
||||
person_block::target_id.nullable().is_not_null(),
|
||||
person_actions::blocked.nullable().is_not_null(),
|
||||
CommunityFollower::select_subscribed_type(),
|
||||
comment_saved::published.nullable().is_not_null(),
|
||||
comment_like::score.nullable(),
|
||||
comment_actions::saved.nullable().is_not_null(),
|
||||
comment_actions::like_score.nullable(),
|
||||
aliases::person2.fields(person::all_columns).nullable(),
|
||||
))
|
||||
};
|
||||
|
@ -167,19 +156,10 @@ fn queries<'a>() -> Queries<
|
|||
|
||||
// If its not an admin, get only the ones you mod
|
||||
if !user.local_user.admin {
|
||||
query
|
||||
.inner_join(
|
||||
community_moderator::table.on(
|
||||
community_moderator::community_id
|
||||
.eq(post::community_id)
|
||||
.and(community_moderator::person_id.eq(user.person.id)),
|
||||
),
|
||||
)
|
||||
.load::<CommentReportView>(&mut conn)
|
||||
.await
|
||||
} else {
|
||||
query.load::<CommentReportView>(&mut conn).await
|
||||
query = query.filter(community_actions::became_moderator.is_not_null());
|
||||
}
|
||||
|
||||
query.load::<CommentReportView>(&mut conn).await
|
||||
};
|
||||
|
||||
Queries::new(read, list)
|
||||
|
@ -222,10 +202,11 @@ impl CommentReportView {
|
|||
if !admin {
|
||||
query
|
||||
.inner_join(
|
||||
community_moderator::table.on(
|
||||
community_moderator::community_id
|
||||
community_actions::table.on(
|
||||
community_actions::community_id
|
||||
.eq(post::community_id)
|
||||
.and(community_moderator::person_id.eq(my_person_id)),
|
||||
.and(community_actions::person_id.eq(my_person_id))
|
||||
.and(community_actions::became_moderator.is_not_null()),
|
||||
),
|
||||
)
|
||||
.select(count(comment_report::id))
|
||||
|
|
|
@ -3,11 +3,8 @@ use diesel::{
|
|||
dsl::{exists, not},
|
||||
pg::Pg,
|
||||
result::Error,
|
||||
sql_types,
|
||||
BoolExpressionMethods,
|
||||
BoxableExpression,
|
||||
ExpressionMethods,
|
||||
IntoSql,
|
||||
JoinOnDsl,
|
||||
NullableExpressionMethods,
|
||||
PgTextExpressionMethods,
|
||||
|
@ -16,23 +13,20 @@ use diesel::{
|
|||
use diesel_async::RunQueryDsl;
|
||||
use diesel_ltree::{nlevel, subpath, Ltree, LtreeExtensions};
|
||||
use lemmy_db_schema::{
|
||||
aliases::creator_community_actions,
|
||||
impls::local_user::LocalUserOptionHelper,
|
||||
newtypes::{CommentId, CommunityId, LocalUserId, PersonId, PostId},
|
||||
schema::{
|
||||
comment,
|
||||
comment_actions,
|
||||
comment_aggregates,
|
||||
comment_like,
|
||||
comment_saved,
|
||||
community,
|
||||
community_block,
|
||||
community_follower,
|
||||
community_moderator,
|
||||
community_person_ban,
|
||||
instance_block,
|
||||
community_actions,
|
||||
instance_actions,
|
||||
local_user,
|
||||
local_user_language,
|
||||
person,
|
||||
person_block,
|
||||
person_actions,
|
||||
post,
|
||||
},
|
||||
source::{
|
||||
|
@ -40,7 +34,17 @@ use lemmy_db_schema::{
|
|||
local_user::LocalUser,
|
||||
site::Site,
|
||||
},
|
||||
utils::{fuzzy_search, limit_and_offset, DbConn, DbPool, ListFn, Queries, ReadFn},
|
||||
utils::{
|
||||
actions,
|
||||
actions_alias,
|
||||
fuzzy_search,
|
||||
limit_and_offset,
|
||||
DbConn,
|
||||
DbPool,
|
||||
ListFn,
|
||||
Queries,
|
||||
ReadFn,
|
||||
},
|
||||
CommentSortType,
|
||||
CommunityVisibility,
|
||||
ListingType,
|
||||
|
@ -50,64 +54,6 @@ fn queries<'a>() -> Queries<
|
|||
impl ReadFn<'a, CommentView, (CommentId, Option<&'a LocalUser>)>,
|
||||
impl ListFn<'a, CommentView, (CommentQuery<'a>, &'a Site)>,
|
||||
> {
|
||||
let is_creator_banned_from_community = exists(
|
||||
community_person_ban::table.filter(
|
||||
community::id
|
||||
.eq(community_person_ban::community_id)
|
||||
.and(community_person_ban::person_id.eq(comment::creator_id)),
|
||||
),
|
||||
);
|
||||
|
||||
let is_local_user_banned_from_community = |person_id| {
|
||||
exists(
|
||||
community_person_ban::table.filter(
|
||||
community::id
|
||||
.eq(community_person_ban::community_id)
|
||||
.and(community_person_ban::person_id.eq(person_id)),
|
||||
),
|
||||
)
|
||||
};
|
||||
|
||||
let is_community_followed = |person_id| {
|
||||
community_follower::table
|
||||
.filter(
|
||||
post::community_id
|
||||
.eq(community_follower::community_id)
|
||||
.and(community_follower::person_id.eq(person_id)),
|
||||
)
|
||||
.select(CommunityFollower::select_subscribed_type())
|
||||
.single_value()
|
||||
};
|
||||
|
||||
let is_creator_blocked = |person_id| {
|
||||
exists(
|
||||
person_block::table.filter(
|
||||
comment::creator_id
|
||||
.eq(person_block::target_id)
|
||||
.and(person_block::person_id.eq(person_id)),
|
||||
),
|
||||
)
|
||||
};
|
||||
|
||||
let score = |person_id| {
|
||||
comment_like::table
|
||||
.filter(
|
||||
comment::id
|
||||
.eq(comment_like::comment_id)
|
||||
.and(comment_like::person_id.eq(person_id)),
|
||||
)
|
||||
.select(comment_like::score.nullable())
|
||||
.single_value()
|
||||
};
|
||||
|
||||
let creator_is_moderator = exists(
|
||||
community_moderator::table.filter(
|
||||
community::id
|
||||
.eq(community_moderator::community_id)
|
||||
.and(community_moderator::person_id.eq(comment::creator_id)),
|
||||
),
|
||||
);
|
||||
|
||||
let creator_is_admin = exists(
|
||||
local_user::table.filter(
|
||||
comment::creator_id
|
||||
|
@ -117,67 +63,56 @@ fn queries<'a>() -> Queries<
|
|||
);
|
||||
|
||||
let all_joins = move |query: comment::BoxedQuery<'a, Pg>, my_person_id: Option<PersonId>| {
|
||||
let is_local_user_banned_from_community_selection: Box<
|
||||
dyn BoxableExpression<_, Pg, SqlType = sql_types::Bool>,
|
||||
> = if let Some(person_id) = my_person_id {
|
||||
Box::new(is_local_user_banned_from_community(person_id))
|
||||
} else {
|
||||
Box::new(false.into_sql::<sql_types::Bool>())
|
||||
};
|
||||
|
||||
let score_selection: Box<
|
||||
dyn BoxableExpression<_, Pg, SqlType = sql_types::Nullable<sql_types::SmallInt>>,
|
||||
> = if let Some(person_id) = my_person_id {
|
||||
Box::new(score(person_id))
|
||||
} else {
|
||||
Box::new(None::<i16>.into_sql::<sql_types::Nullable<sql_types::SmallInt>>())
|
||||
};
|
||||
|
||||
let subscribed_type_selection: Box<
|
||||
dyn BoxableExpression<
|
||||
_,
|
||||
Pg,
|
||||
SqlType = sql_types::Nullable<lemmy_db_schema::schema::sql_types::CommunityFollowerState>,
|
||||
>,
|
||||
> = if let Some(person_id) = my_person_id {
|
||||
Box::new(is_community_followed(person_id))
|
||||
} else {
|
||||
Box::new(None::<CommunityFollowerState>.into_sql::<sql_types::Nullable<lemmy_db_schema::schema::sql_types::CommunityFollowerState>>())
|
||||
};
|
||||
|
||||
let is_creator_blocked_selection: Box<dyn BoxableExpression<_, Pg, SqlType = sql_types::Bool>> =
|
||||
if let Some(person_id) = my_person_id {
|
||||
Box::new(is_creator_blocked(person_id))
|
||||
} else {
|
||||
Box::new(false.into_sql::<sql_types::Bool>())
|
||||
};
|
||||
|
||||
query
|
||||
.inner_join(person::table)
|
||||
.inner_join(post::table)
|
||||
.inner_join(community::table.on(post::community_id.eq(community::id)))
|
||||
.inner_join(comment_aggregates::table)
|
||||
.left_join(
|
||||
comment_saved::table.on(
|
||||
comment::id
|
||||
.eq(comment_saved::comment_id)
|
||||
.and(comment_saved::person_id.eq(my_person_id.unwrap_or(PersonId(-1)))),
|
||||
),
|
||||
)
|
||||
.left_join(actions(
|
||||
community_actions::table,
|
||||
my_person_id,
|
||||
post::community_id,
|
||||
))
|
||||
.left_join(actions(
|
||||
comment_actions::table,
|
||||
my_person_id,
|
||||
comment_aggregates::comment_id,
|
||||
))
|
||||
.left_join(actions(
|
||||
person_actions::table,
|
||||
my_person_id,
|
||||
comment::creator_id,
|
||||
))
|
||||
.left_join(actions(
|
||||
instance_actions::table,
|
||||
my_person_id,
|
||||
community::instance_id,
|
||||
))
|
||||
.left_join(actions_alias(
|
||||
creator_community_actions,
|
||||
comment::creator_id,
|
||||
post::community_id,
|
||||
))
|
||||
.select((
|
||||
comment::all_columns,
|
||||
person::all_columns,
|
||||
post::all_columns,
|
||||
community::all_columns,
|
||||
comment_aggregates::all_columns,
|
||||
is_creator_banned_from_community,
|
||||
is_local_user_banned_from_community_selection,
|
||||
creator_is_moderator,
|
||||
creator_community_actions
|
||||
.field(community_actions::received_ban)
|
||||
.nullable()
|
||||
.is_not_null(),
|
||||
community_actions::received_ban.nullable().is_not_null(),
|
||||
creator_community_actions
|
||||
.field(community_actions::became_moderator)
|
||||
.nullable()
|
||||
.is_not_null(),
|
||||
creator_is_admin,
|
||||
subscribed_type_selection,
|
||||
comment_saved::person_id.nullable().is_not_null(),
|
||||
is_creator_blocked_selection,
|
||||
score_selection,
|
||||
CommunityFollower::select_subscribed_type(),
|
||||
comment_actions::saved.nullable().is_not_null(),
|
||||
person_actions::blocked.nullable().is_not_null(),
|
||||
comment_actions::like_score.nullable(),
|
||||
))
|
||||
};
|
||||
|
||||
|
@ -197,15 +132,7 @@ fn queries<'a>() -> Queries<
|
|||
query = query.filter(
|
||||
community::visibility
|
||||
.ne(CommunityVisibility::Private)
|
||||
.or(exists(
|
||||
community_follower::table.filter(
|
||||
post::community_id.eq(community_follower::community_id).and(
|
||||
community_follower::person_id
|
||||
.eq(my_local_user.map(|l| l.person_id).unwrap_or_default())
|
||||
.and(community_follower::state.eq(CommunityFollowerState::Accepted)),
|
||||
),
|
||||
),
|
||||
)),
|
||||
.or(community_actions::follow_state.eq(CommunityFollowerState::Accepted)),
|
||||
);
|
||||
}
|
||||
query.first(&mut conn).await
|
||||
|
@ -213,7 +140,6 @@ fn queries<'a>() -> Queries<
|
|||
|
||||
let list = move |mut conn: DbConn<'a>, (options, site): (CommentQuery<'a>, &'a Site)| async move {
|
||||
// The left join below will return None in this case
|
||||
let person_id_join = options.local_user.person_id().unwrap_or(PersonId(-1));
|
||||
let local_user_id_join = options
|
||||
.local_user
|
||||
.local_user_id()
|
||||
|
@ -245,13 +171,7 @@ fn queries<'a>() -> Queries<
|
|||
query = query.filter(post::community_id.eq(community_id));
|
||||
}
|
||||
|
||||
let is_subscribed = exists(
|
||||
community_follower::table.filter(
|
||||
post::community_id
|
||||
.eq(community_follower::community_id)
|
||||
.and(community_follower::person_id.eq(person_id_join)),
|
||||
),
|
||||
);
|
||||
let is_subscribed = community_actions::followed.is_not_null();
|
||||
|
||||
match options.listing_type.unwrap_or_default() {
|
||||
ListingType::Subscribed => query = query.filter(is_subscribed), /* TODO could be this: and(community_follower::person_id.eq(person_id_join)), */
|
||||
|
@ -262,29 +182,27 @@ fn queries<'a>() -> Queries<
|
|||
}
|
||||
ListingType::All => query = query.filter(community::hidden.eq(false).or(is_subscribed)),
|
||||
ListingType::ModeratorView => {
|
||||
query = query.filter(exists(
|
||||
community_moderator::table.filter(
|
||||
post::community_id
|
||||
.eq(community_moderator::community_id)
|
||||
.and(community_moderator::person_id.eq(person_id_join)),
|
||||
),
|
||||
));
|
||||
query = query.filter(community_actions::became_moderator.is_not_null());
|
||||
}
|
||||
}
|
||||
|
||||
// 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_saved::person_id.is_not_null())
|
||||
.then_order_by(comment_saved::published.desc());
|
||||
.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() {
|
||||
query = query.filter(not_creator_filter).filter(score(my_id).eq(1));
|
||||
query = query
|
||||
.filter(not_creator_filter)
|
||||
.filter(comment_actions::like_score.eq(1));
|
||||
} else if options.disliked_only.unwrap_or_default() {
|
||||
query = query.filter(not_creator_filter).filter(score(my_id).eq(-1));
|
||||
query = query
|
||||
.filter(not_creator_filter)
|
||||
.filter(comment_actions::like_score.eq(-1));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -305,21 +223,10 @@ fn queries<'a>() -> Queries<
|
|||
));
|
||||
|
||||
// Don't show blocked communities or persons
|
||||
query = query.filter(not(exists(
|
||||
instance_block::table.filter(
|
||||
community::instance_id
|
||||
.eq(instance_block::instance_id)
|
||||
.and(instance_block::person_id.eq(person_id_join)),
|
||||
),
|
||||
)));
|
||||
query = query.filter(not(exists(
|
||||
community_block::table.filter(
|
||||
community::id
|
||||
.eq(community_block::community_id)
|
||||
.and(community_block::person_id.eq(person_id_join)),
|
||||
),
|
||||
)));
|
||||
query = query.filter(not(is_creator_blocked(person_id_join)));
|
||||
query = query
|
||||
.filter(instance_actions::blocked.is_null())
|
||||
.filter(community_actions::blocked.is_null())
|
||||
.filter(person_actions::blocked.is_null());
|
||||
};
|
||||
|
||||
if !options.local_user.show_nsfw(site) {
|
||||
|
@ -334,15 +241,7 @@ fn queries<'a>() -> Queries<
|
|||
query = query.filter(
|
||||
community::visibility
|
||||
.ne(CommunityVisibility::Private)
|
||||
.or(exists(
|
||||
community_follower::table.filter(
|
||||
post::community_id.eq(community_follower::community_id).and(
|
||||
community_follower::person_id
|
||||
.eq(person_id_join)
|
||||
.and(community_follower::state.eq(CommunityFollowerState::Accepted)),
|
||||
),
|
||||
),
|
||||
)),
|
||||
.or(community_actions::follow_state.eq(CommunityFollowerState::Accepted)),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -10,27 +10,23 @@ use diesel::{
|
|||
};
|
||||
use diesel_async::RunQueryDsl;
|
||||
use lemmy_db_schema::{
|
||||
aliases,
|
||||
aliases::{self, creator_community_actions},
|
||||
newtypes::{CommunityId, PersonId, PostId, PostReportId},
|
||||
schema::{
|
||||
community,
|
||||
community_follower,
|
||||
community_moderator,
|
||||
community_person_ban,
|
||||
community_actions,
|
||||
local_user,
|
||||
person,
|
||||
person_block,
|
||||
person_post_aggregates,
|
||||
person_actions,
|
||||
post,
|
||||
post_actions,
|
||||
post_aggregates,
|
||||
post_hide,
|
||||
post_like,
|
||||
post_read,
|
||||
post_report,
|
||||
post_saved,
|
||||
},
|
||||
source::community::CommunityFollower,
|
||||
utils::{
|
||||
actions,
|
||||
actions_alias,
|
||||
functions::coalesce,
|
||||
get_conn,
|
||||
limit_and_offset,
|
||||
|
@ -52,25 +48,16 @@ fn queries<'a>() -> Queries<
|
|||
.inner_join(community::table.on(post::community_id.eq(community::id)))
|
||||
.inner_join(person::table.on(post_report::creator_id.eq(person::id)))
|
||||
.inner_join(aliases::person1.on(post::creator_id.eq(aliases::person1.field(person::id))))
|
||||
.left_join(
|
||||
community_person_ban::table.on(
|
||||
post::community_id
|
||||
.eq(community_person_ban::community_id)
|
||||
.and(community_person_ban::person_id.eq(post::creator_id)),
|
||||
),
|
||||
)
|
||||
.left_join(
|
||||
aliases::community_moderator1.on(
|
||||
aliases::community_moderator1
|
||||
.field(community_moderator::community_id)
|
||||
.eq(post::community_id)
|
||||
.and(
|
||||
aliases::community_moderator1
|
||||
.field(community_moderator::person_id)
|
||||
.eq(my_person_id),
|
||||
),
|
||||
),
|
||||
)
|
||||
.left_join(actions_alias(
|
||||
creator_community_actions,
|
||||
post::creator_id,
|
||||
post::community_id,
|
||||
))
|
||||
.left_join(actions(
|
||||
community_actions::table,
|
||||
Some(my_person_id),
|
||||
post::community_id,
|
||||
))
|
||||
.left_join(
|
||||
local_user::table.on(
|
||||
post::creator_id
|
||||
|
@ -78,55 +65,12 @@ fn queries<'a>() -> Queries<
|
|||
.and(local_user::admin.eq(true)),
|
||||
),
|
||||
)
|
||||
.left_join(
|
||||
post_saved::table.on(
|
||||
post::id
|
||||
.eq(post_saved::post_id)
|
||||
.and(post_saved::person_id.eq(my_person_id)),
|
||||
),
|
||||
)
|
||||
.left_join(
|
||||
post_read::table.on(
|
||||
post::id
|
||||
.eq(post_read::post_id)
|
||||
.and(post_read::person_id.eq(my_person_id)),
|
||||
),
|
||||
)
|
||||
.left_join(
|
||||
post_hide::table.on(
|
||||
post::id
|
||||
.eq(post_hide::post_id)
|
||||
.and(post_hide::person_id.eq(my_person_id)),
|
||||
),
|
||||
)
|
||||
.left_join(
|
||||
person_block::table.on(
|
||||
post::creator_id
|
||||
.eq(person_block::target_id)
|
||||
.and(person_block::person_id.eq(my_person_id)),
|
||||
),
|
||||
)
|
||||
.left_join(
|
||||
person_post_aggregates::table.on(
|
||||
post::id
|
||||
.eq(person_post_aggregates::post_id)
|
||||
.and(person_post_aggregates::person_id.eq(my_person_id)),
|
||||
),
|
||||
)
|
||||
.left_join(
|
||||
community_follower::table.on(
|
||||
post::community_id
|
||||
.eq(community_follower::community_id)
|
||||
.and(community_follower::person_id.eq(my_person_id)),
|
||||
),
|
||||
)
|
||||
.left_join(
|
||||
post_like::table.on(
|
||||
post::id
|
||||
.eq(post_like::post_id)
|
||||
.and(post_like::person_id.eq(my_person_id)),
|
||||
),
|
||||
)
|
||||
.left_join(actions(post_actions::table, Some(my_person_id), post::id))
|
||||
.left_join(actions(
|
||||
person_actions::table,
|
||||
Some(my_person_id),
|
||||
post::creator_id,
|
||||
))
|
||||
.inner_join(post_aggregates::table.on(post_report::post_id.eq(post_aggregates::post_id)))
|
||||
.left_join(
|
||||
aliases::person2
|
||||
|
@ -138,20 +82,23 @@ fn queries<'a>() -> Queries<
|
|||
community::all_columns,
|
||||
person::all_columns,
|
||||
aliases::person1.fields(person::all_columns),
|
||||
community_person_ban::community_id.nullable().is_not_null(),
|
||||
aliases::community_moderator1
|
||||
.field(community_moderator::community_id)
|
||||
creator_community_actions
|
||||
.field(community_actions::received_ban)
|
||||
.nullable()
|
||||
.is_not_null(),
|
||||
creator_community_actions
|
||||
.field(community_actions::became_moderator)
|
||||
.nullable()
|
||||
.is_not_null(),
|
||||
local_user::admin.nullable().is_not_null(),
|
||||
CommunityFollower::select_subscribed_type(),
|
||||
post_saved::post_id.nullable().is_not_null(),
|
||||
post_read::post_id.nullable().is_not_null(),
|
||||
post_hide::post_id.nullable().is_not_null(),
|
||||
person_block::target_id.nullable().is_not_null(),
|
||||
post_like::score.nullable(),
|
||||
post_actions::saved.nullable().is_not_null(),
|
||||
post_actions::read.nullable().is_not_null(),
|
||||
post_actions::hidden.nullable().is_not_null(),
|
||||
person_actions::blocked.nullable().is_not_null(),
|
||||
post_actions::like_score.nullable(),
|
||||
coalesce(
|
||||
post_aggregates::comments.nullable() - person_post_aggregates::read_comments.nullable(),
|
||||
post_aggregates::comments.nullable() - post_actions::read_comments_amount.nullable(),
|
||||
post_aggregates::comments,
|
||||
),
|
||||
post_aggregates::all_columns,
|
||||
|
@ -195,19 +142,10 @@ fn queries<'a>() -> Queries<
|
|||
|
||||
// If its not an admin, get only the ones you mod
|
||||
if !user.local_user.admin {
|
||||
query
|
||||
.inner_join(
|
||||
community_moderator::table.on(
|
||||
community_moderator::community_id
|
||||
.eq(post::community_id)
|
||||
.and(community_moderator::person_id.eq(user.person.id)),
|
||||
),
|
||||
)
|
||||
.load::<PostReportView>(&mut conn)
|
||||
.await
|
||||
} else {
|
||||
query.load::<PostReportView>(&mut conn).await
|
||||
query = query.filter(community_actions::became_moderator.is_not_null());
|
||||
}
|
||||
|
||||
query.load::<PostReportView>(&mut conn).await
|
||||
};
|
||||
|
||||
Queries::new(read, list)
|
||||
|
@ -247,10 +185,11 @@ impl PostReportView {
|
|||
if !admin {
|
||||
query
|
||||
.inner_join(
|
||||
community_moderator::table.on(
|
||||
community_moderator::community_id
|
||||
community_actions::table.on(
|
||||
community_actions::community_id
|
||||
.eq(post::community_id)
|
||||
.and(community_moderator::person_id.eq(my_person_id)),
|
||||
.and(community_actions::person_id.eq(my_person_id))
|
||||
.and(community_actions::became_moderator.is_not_null()),
|
||||
),
|
||||
)
|
||||
.select(count(post_report::id))
|
||||
|
|
|
@ -5,11 +5,8 @@ use diesel::{
|
|||
pg::Pg,
|
||||
query_builder::AsQuery,
|
||||
result::Error,
|
||||
sql_types,
|
||||
BoolExpressionMethods,
|
||||
BoxableExpression,
|
||||
ExpressionMethods,
|
||||
IntoSql,
|
||||
JoinOnDsl,
|
||||
NullableExpressionMethods,
|
||||
OptionalExtension,
|
||||
|
@ -20,27 +17,21 @@ use diesel_async::RunQueryDsl;
|
|||
use i_love_jesus::PaginatedQueryBuilder;
|
||||
use lemmy_db_schema::{
|
||||
aggregates::structs::{post_aggregates_keys as key, PostAggregates},
|
||||
aliases::creator_community_actions,
|
||||
impls::local_user::LocalUserOptionHelper,
|
||||
newtypes::{CommunityId, LocalUserId, PersonId, PostId},
|
||||
schema::{
|
||||
community,
|
||||
community_block,
|
||||
community_follower,
|
||||
community_moderator,
|
||||
community_person_ban,
|
||||
community_actions,
|
||||
image_details,
|
||||
instance_block,
|
||||
instance_actions,
|
||||
local_user,
|
||||
local_user_language,
|
||||
person,
|
||||
person_block,
|
||||
person_post_aggregates,
|
||||
person_actions,
|
||||
post,
|
||||
post_actions,
|
||||
post_aggregates,
|
||||
post_hide,
|
||||
post_like,
|
||||
post_read,
|
||||
post_saved,
|
||||
},
|
||||
source::{
|
||||
community::{CommunityFollower, CommunityFollowerState},
|
||||
|
@ -48,6 +39,9 @@ use lemmy_db_schema::{
|
|||
site::Site,
|
||||
},
|
||||
utils::{
|
||||
action_query,
|
||||
actions,
|
||||
actions_alias,
|
||||
functions::coalesce,
|
||||
fuzzy_search,
|
||||
get_conn,
|
||||
|
@ -72,32 +66,6 @@ fn queries<'a>() -> Queries<
|
|||
impl ReadFn<'a, PostView, (PostId, Option<&'a LocalUser>, bool)>,
|
||||
impl ListFn<'a, PostView, (PostQuery<'a>, &'a Site)>,
|
||||
> {
|
||||
let is_creator_banned_from_community = exists(
|
||||
community_person_ban::table.filter(
|
||||
post_aggregates::community_id
|
||||
.eq(community_person_ban::community_id)
|
||||
.and(community_person_ban::person_id.eq(post_aggregates::creator_id)),
|
||||
),
|
||||
);
|
||||
|
||||
let is_local_user_banned_from_community = |person_id| {
|
||||
exists(
|
||||
community_person_ban::table.filter(
|
||||
post_aggregates::community_id
|
||||
.eq(community_person_ban::community_id)
|
||||
.and(community_person_ban::person_id.eq(person_id)),
|
||||
),
|
||||
)
|
||||
};
|
||||
|
||||
let creator_is_moderator = exists(
|
||||
community_moderator::table.filter(
|
||||
post_aggregates::community_id
|
||||
.eq(community_moderator::community_id)
|
||||
.and(community_moderator::person_id.eq(post_aggregates::creator_id)),
|
||||
),
|
||||
);
|
||||
|
||||
let creator_is_admin = exists(
|
||||
local_user::table.filter(
|
||||
post_aggregates::creator_id
|
||||
|
@ -106,155 +74,63 @@ fn queries<'a>() -> Queries<
|
|||
),
|
||||
);
|
||||
|
||||
let is_read = |person_id| {
|
||||
exists(
|
||||
post_read::table.filter(
|
||||
post_aggregates::post_id
|
||||
.eq(post_read::post_id)
|
||||
.and(post_read::person_id.eq(person_id)),
|
||||
),
|
||||
)
|
||||
};
|
||||
|
||||
let is_hidden = |person_id| {
|
||||
exists(
|
||||
post_hide::table.filter(
|
||||
post_aggregates::post_id
|
||||
.eq(post_hide::post_id)
|
||||
.and(post_hide::person_id.eq(person_id)),
|
||||
),
|
||||
)
|
||||
};
|
||||
|
||||
let is_creator_blocked = |person_id| {
|
||||
exists(
|
||||
person_block::table.filter(
|
||||
post_aggregates::creator_id
|
||||
.eq(person_block::target_id)
|
||||
.and(person_block::person_id.eq(person_id)),
|
||||
),
|
||||
)
|
||||
};
|
||||
|
||||
let score = |person_id| {
|
||||
post_like::table
|
||||
.filter(
|
||||
post_aggregates::post_id
|
||||
.eq(post_like::post_id)
|
||||
.and(post_like::person_id.eq(person_id)),
|
||||
)
|
||||
.select(post_like::score.nullable())
|
||||
.single_value()
|
||||
};
|
||||
|
||||
// TODO maybe this should go to localuser also
|
||||
let all_joins = move |query: post_aggregates::BoxedQuery<'a, Pg>,
|
||||
my_person_id: Option<PersonId>| {
|
||||
let is_local_user_banned_from_community_selection: Box<
|
||||
dyn BoxableExpression<_, Pg, SqlType = sql_types::Bool>,
|
||||
> = if let Some(person_id) = my_person_id {
|
||||
Box::new(is_local_user_banned_from_community(person_id))
|
||||
} else {
|
||||
Box::new(false.into_sql::<sql_types::Bool>())
|
||||
};
|
||||
|
||||
let is_read_selection: Box<dyn BoxableExpression<_, Pg, SqlType = sql_types::Bool>> =
|
||||
if let Some(person_id) = my_person_id {
|
||||
Box::new(is_read(person_id))
|
||||
} else {
|
||||
Box::new(false.into_sql::<sql_types::Bool>())
|
||||
};
|
||||
|
||||
let is_hidden_selection: Box<dyn BoxableExpression<_, Pg, SqlType = sql_types::Bool>> =
|
||||
if let Some(person_id) = my_person_id {
|
||||
Box::new(is_hidden(person_id))
|
||||
} else {
|
||||
Box::new(false.into_sql::<sql_types::Bool>())
|
||||
};
|
||||
|
||||
let is_creator_blocked_selection: Box<dyn BoxableExpression<_, Pg, SqlType = sql_types::Bool>> =
|
||||
if let Some(person_id) = my_person_id {
|
||||
Box::new(is_creator_blocked(person_id))
|
||||
} else {
|
||||
Box::new(false.into_sql::<sql_types::Bool>())
|
||||
};
|
||||
|
||||
let subscribed_type_selection: Box<
|
||||
dyn BoxableExpression<
|
||||
_,
|
||||
Pg,
|
||||
SqlType = sql_types::Nullable<lemmy_db_schema::schema::sql_types::CommunityFollowerState>,
|
||||
>,
|
||||
> = if let Some(person_id) = my_person_id {
|
||||
Box::new(
|
||||
community_follower::table
|
||||
.filter(
|
||||
post_aggregates::community_id
|
||||
.eq(community_follower::community_id)
|
||||
.and(community_follower::person_id.eq(person_id)),
|
||||
)
|
||||
.select(CommunityFollower::select_subscribed_type())
|
||||
.single_value(),
|
||||
)
|
||||
} else {
|
||||
Box::new(None::<CommunityFollowerState>.into_sql::<sql_types::Nullable<lemmy_db_schema::schema::sql_types::CommunityFollowerState>>())
|
||||
};
|
||||
|
||||
let score_selection: Box<
|
||||
dyn BoxableExpression<_, Pg, SqlType = sql_types::Nullable<sql_types::SmallInt>>,
|
||||
> = if let Some(person_id) = my_person_id {
|
||||
Box::new(score(person_id))
|
||||
} else {
|
||||
Box::new(None::<i16>.into_sql::<sql_types::Nullable<sql_types::SmallInt>>())
|
||||
};
|
||||
|
||||
let read_comments: Box<
|
||||
dyn BoxableExpression<_, Pg, SqlType = sql_types::Nullable<sql_types::BigInt>>,
|
||||
> = if let Some(person_id) = my_person_id {
|
||||
Box::new(
|
||||
person_post_aggregates::table
|
||||
.filter(
|
||||
post_aggregates::post_id
|
||||
.eq(person_post_aggregates::post_id)
|
||||
.and(person_post_aggregates::person_id.eq(person_id)),
|
||||
)
|
||||
.select(person_post_aggregates::read_comments.nullable())
|
||||
.single_value(),
|
||||
)
|
||||
} else {
|
||||
Box::new(None::<i64>.into_sql::<sql_types::Nullable<sql_types::BigInt>>())
|
||||
};
|
||||
|
||||
query
|
||||
.inner_join(person::table)
|
||||
.inner_join(community::table)
|
||||
.inner_join(post::table)
|
||||
.left_join(image_details::table.on(post::thumbnail_url.eq(image_details::link.nullable())))
|
||||
.left_join(
|
||||
post_saved::table.on(
|
||||
post_aggregates::post_id
|
||||
.eq(post_saved::post_id)
|
||||
.and(post_saved::person_id.eq(my_person_id.unwrap_or(PersonId(-1)))),
|
||||
),
|
||||
)
|
||||
.left_join(actions(
|
||||
community_actions::table,
|
||||
my_person_id,
|
||||
post_aggregates::community_id,
|
||||
))
|
||||
.left_join(actions(
|
||||
person_actions::table,
|
||||
my_person_id,
|
||||
post_aggregates::creator_id,
|
||||
))
|
||||
.left_join(actions(
|
||||
post_actions::table,
|
||||
my_person_id,
|
||||
post_aggregates::post_id,
|
||||
))
|
||||
.left_join(actions(
|
||||
instance_actions::table,
|
||||
my_person_id,
|
||||
post_aggregates::instance_id,
|
||||
))
|
||||
.left_join(actions_alias(
|
||||
creator_community_actions,
|
||||
post_aggregates::creator_id,
|
||||
post_aggregates::community_id,
|
||||
))
|
||||
.select((
|
||||
post::all_columns,
|
||||
person::all_columns,
|
||||
community::all_columns,
|
||||
image_details::all_columns.nullable(),
|
||||
is_creator_banned_from_community,
|
||||
is_local_user_banned_from_community_selection,
|
||||
creator_is_moderator,
|
||||
creator_community_actions
|
||||
.field(community_actions::received_ban)
|
||||
.nullable()
|
||||
.is_not_null(),
|
||||
community_actions::received_ban.nullable().is_not_null(),
|
||||
creator_community_actions
|
||||
.field(community_actions::became_moderator)
|
||||
.nullable()
|
||||
.is_not_null(),
|
||||
creator_is_admin,
|
||||
post_aggregates::all_columns,
|
||||
subscribed_type_selection,
|
||||
post_saved::person_id.nullable().is_not_null(),
|
||||
is_read_selection,
|
||||
is_hidden_selection,
|
||||
is_creator_blocked_selection,
|
||||
score_selection,
|
||||
CommunityFollower::select_subscribed_type(),
|
||||
post_actions::saved.nullable().is_not_null(),
|
||||
post_actions::read.nullable().is_not_null(),
|
||||
post_actions::hidden.nullable().is_not_null(),
|
||||
person_actions::blocked.nullable().is_not_null(),
|
||||
post_actions::like_score.nullable(),
|
||||
coalesce(
|
||||
post_aggregates::comments.nullable() - read_comments,
|
||||
post_aggregates::comments.nullable() - post_actions::read_comments_amount.nullable(),
|
||||
post_aggregates::comments,
|
||||
),
|
||||
))
|
||||
|
@ -305,17 +181,7 @@ fn queries<'a>() -> Queries<
|
|||
.filter(
|
||||
community::visibility
|
||||
.ne(CommunityVisibility::Private)
|
||||
.or(exists(
|
||||
community_follower::table.filter(
|
||||
post_aggregates::community_id
|
||||
.eq(community_follower::community_id)
|
||||
.and(
|
||||
community_follower::person_id
|
||||
.eq(my_local_user.map(|l| l.person_id).unwrap_or_default())
|
||||
.and(community_follower::state.eq(CommunityFollowerState::Accepted)),
|
||||
),
|
||||
),
|
||||
)),
|
||||
.or(community_actions::follow_state.eq(CommunityFollowerState::Accepted)),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -329,7 +195,6 @@ fn queries<'a>() -> Queries<
|
|||
|
||||
let list = move |mut conn: DbConn<'a>, (options, site): (PostQuery<'a>, &'a Site)| async move {
|
||||
// The left join below will return None in this case
|
||||
let person_id_join = options.local_user.person_id().unwrap_or(PersonId(-1));
|
||||
let local_user_id_join = options
|
||||
.local_user
|
||||
.local_user_id()
|
||||
|
@ -371,13 +236,7 @@ fn queries<'a>() -> Queries<
|
|||
query = query.filter(post_aggregates::creator_id.eq(creator_id));
|
||||
}
|
||||
|
||||
let is_subscribed = exists(
|
||||
community_follower::table.filter(
|
||||
post_aggregates::community_id
|
||||
.eq(community_follower::community_id)
|
||||
.and(community_follower::person_id.eq(person_id_join)),
|
||||
),
|
||||
);
|
||||
let is_subscribed = community_actions::followed.is_not_null();
|
||||
match options.listing_type.unwrap_or_default() {
|
||||
ListingType::Subscribed => query = query.filter(is_subscribed),
|
||||
ListingType::Local => {
|
||||
|
@ -387,13 +246,7 @@ fn queries<'a>() -> Queries<
|
|||
}
|
||||
ListingType::All => query = query.filter(community::hidden.eq(false).or(is_subscribed)),
|
||||
ListingType::ModeratorView => {
|
||||
query = query.filter(exists(
|
||||
community_moderator::table.filter(
|
||||
post::community_id
|
||||
.eq(community_moderator::community_id)
|
||||
.and(community_moderator::person_id.eq(person_id_join)),
|
||||
),
|
||||
));
|
||||
query = query.filter(community_actions::became_moderator.is_not_null());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -434,8 +287,8 @@ 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(post_saved::person_id.is_not_null())
|
||||
.then_order_by(post_saved::published.desc());
|
||||
.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.
|
||||
|
@ -445,24 +298,26 @@ fn queries<'a>() -> Queries<
|
|||
{
|
||||
// Do not hide read posts when it is a user profile view
|
||||
// Or, only hide read posts on non-profile views
|
||||
if let (None, Some(person_id)) = (options.creator_id, options.local_user.person_id()) {
|
||||
query = query.filter(not(is_read(person_id)));
|
||||
if options.creator_id.is_none() {
|
||||
query = query.filter(post_actions::read.is_null());
|
||||
}
|
||||
}
|
||||
|
||||
if !options.show_hidden.unwrap_or_default() {
|
||||
// If a creator id isn't given (IE its on home or community pages), hide the hidden posts
|
||||
if let (None, Some(person_id)) = (options.creator_id, options.local_user.person_id()) {
|
||||
query = query.filter(not(is_hidden(person_id)));
|
||||
}
|
||||
// If a creator id isn't given (IE its on home or community pages), hide the hidden posts
|
||||
if !options.show_hidden.unwrap_or_default() && options.creator_id.is_none() {
|
||||
query = query.filter(post_actions::hidden.is_null());
|
||||
}
|
||||
|
||||
if let Some(my_id) = options.local_user.person_id() {
|
||||
let not_creator_filter = post_aggregates::creator_id.ne(my_id);
|
||||
if options.liked_only.unwrap_or_default() {
|
||||
query = query.filter(not_creator_filter).filter(score(my_id).eq(1));
|
||||
query = query
|
||||
.filter(not_creator_filter)
|
||||
.filter(post_actions::like_score.eq(1));
|
||||
} else if options.disliked_only.unwrap_or_default() {
|
||||
query = query.filter(not_creator_filter).filter(score(my_id).eq(-1));
|
||||
query = query
|
||||
.filter(not_creator_filter)
|
||||
.filter(post_actions::like_score.eq(-1));
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -472,47 +327,27 @@ fn queries<'a>() -> Queries<
|
|||
query = query.filter(
|
||||
community::visibility
|
||||
.ne(CommunityVisibility::Private)
|
||||
.or(exists(
|
||||
community_follower::table.filter(
|
||||
post_aggregates::community_id
|
||||
.eq(community_follower::community_id)
|
||||
.and(community_follower::person_id.eq(person_id_join))
|
||||
.and(community_follower::state.eq(CommunityFollowerState::Accepted)),
|
||||
),
|
||||
)),
|
||||
.or(community_actions::follow_state.eq(CommunityFollowerState::Accepted)),
|
||||
);
|
||||
}
|
||||
|
||||
// Dont filter blocks or missing languages for moderator view type
|
||||
if let (Some(person_id), false) = (
|
||||
options.local_user.person_id(),
|
||||
options.listing_type.unwrap_or_default() == ListingType::ModeratorView,
|
||||
) {
|
||||
// Filter out the rows with missing languages
|
||||
query = query.filter(exists(
|
||||
local_user_language::table.filter(
|
||||
post::language_id
|
||||
.eq(local_user_language::language_id)
|
||||
.and(local_user_language::local_user_id.eq(local_user_id_join)),
|
||||
),
|
||||
));
|
||||
if options.listing_type.unwrap_or_default() != ListingType::ModeratorView {
|
||||
// Filter out the rows with missing languages if user is logged in
|
||||
if options.local_user.is_some() {
|
||||
query = query.filter(exists(
|
||||
local_user_language::table.filter(
|
||||
post::language_id
|
||||
.eq(local_user_language::language_id)
|
||||
.and(local_user_language::local_user_id.eq(local_user_id_join)),
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
// Don't show blocked instances, communities or persons
|
||||
query = query.filter(not(exists(
|
||||
community_block::table.filter(
|
||||
post_aggregates::community_id
|
||||
.eq(community_block::community_id)
|
||||
.and(community_block::person_id.eq(person_id_join)),
|
||||
),
|
||||
)));
|
||||
query = query.filter(not(exists(
|
||||
instance_block::table.filter(
|
||||
post_aggregates::instance_id
|
||||
.eq(instance_block::instance_id)
|
||||
.and(instance_block::person_id.eq(person_id_join)),
|
||||
),
|
||||
)));
|
||||
query = query.filter(not(is_creator_blocked(person_id)));
|
||||
query = query.filter(community_actions::blocked.is_null());
|
||||
query = query.filter(instance_actions::blocked.is_null());
|
||||
query = query.filter(person_actions::blocked.is_null());
|
||||
}
|
||||
|
||||
let (limit, offset) = limit_and_offset(options.page, options.limit)?;
|
||||
|
@ -682,13 +517,10 @@ impl<'a> PostQuery<'a> {
|
|||
// covers the "worst case" of the whole page consisting of posts from one community
|
||||
// but using the largest community decreases the pagination-frame so make the real query more
|
||||
// efficient.
|
||||
use lemmy_db_schema::schema::{
|
||||
community_aggregates::dsl::{community_aggregates, community_id, users_active_month},
|
||||
community_follower::dsl::{
|
||||
community_follower,
|
||||
community_id as follower_community_id,
|
||||
person_id,
|
||||
},
|
||||
use lemmy_db_schema::schema::community_aggregates::dsl::{
|
||||
community_aggregates,
|
||||
community_id,
|
||||
users_active_month,
|
||||
};
|
||||
let (limit, offset) = limit_and_offset(self.page, self.limit)?;
|
||||
if offset != 0 && self.page_after.is_some() {
|
||||
|
@ -699,9 +531,9 @@ impl<'a> PostQuery<'a> {
|
|||
let self_person_id = self.local_user.expect("part of the above if").person_id;
|
||||
let largest_subscribed = {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
community_follower
|
||||
.filter(person_id.eq(self_person_id))
|
||||
.inner_join(community_aggregates.on(community_id.eq(follower_community_id)))
|
||||
action_query(community_actions::followed)
|
||||
.filter(community_actions::person_id.eq(self_person_id))
|
||||
.inner_join(community_aggregates.on(community_id.eq(community_actions::community_id)))
|
||||
.order_by(users_active_month.desc())
|
||||
.select(community_id)
|
||||
.limit(1)
|
||||
|
@ -816,7 +648,7 @@ mod tests {
|
|||
site::Site,
|
||||
},
|
||||
traits::{Bannable, Blockable, Crud, Followable, Joinable, Likeable, Saveable},
|
||||
utils::{build_db_pool, build_db_pool_for_tests, get_conn, DbPool, RANK_DEFAULT},
|
||||
utils::{build_db_pool, build_db_pool_for_tests, get_conn, uplete, DbPool, RANK_DEFAULT},
|
||||
CommunityVisibility,
|
||||
PostSortType,
|
||||
SubscribedType,
|
||||
|
@ -1212,7 +1044,7 @@ mod tests {
|
|||
|
||||
let like_removed =
|
||||
PostLike::remove(pool, data.local_user_view.person.id, data.inserted_post.id).await?;
|
||||
assert_eq!(1, like_removed);
|
||||
assert_eq!(uplete::Count::only_deleted(1), like_removed);
|
||||
cleanup(data, pool).await
|
||||
}
|
||||
|
||||
|
|
|
@ -12,8 +12,8 @@ use diesel_async::RunQueryDsl;
|
|||
use lemmy_db_schema::{
|
||||
aliases,
|
||||
newtypes::{PersonId, PrivateMessageId},
|
||||
schema::{instance_block, person, person_block, private_message},
|
||||
utils::{get_conn, limit_and_offset, DbConn, DbPool, ListFn, Queries, ReadFn},
|
||||
schema::{instance_actions, person, person_actions, private_message},
|
||||
utils::{actions, get_conn, limit_and_offset, DbConn, DbPool, ListFn, Queries, ReadFn},
|
||||
};
|
||||
use tracing::debug;
|
||||
|
||||
|
@ -27,20 +27,16 @@ fn queries<'a>() -> Queries<
|
|||
.inner_join(
|
||||
aliases::person1.on(private_message::recipient_id.eq(aliases::person1.field(person::id))),
|
||||
)
|
||||
.left_join(
|
||||
person_block::table.on(
|
||||
private_message::creator_id
|
||||
.eq(person_block::target_id)
|
||||
.and(person_block::person_id.eq(aliases::person1.field(person::id))),
|
||||
),
|
||||
)
|
||||
.left_join(
|
||||
instance_block::table.on(
|
||||
person::instance_id
|
||||
.eq(instance_block::instance_id)
|
||||
.and(instance_block::person_id.eq(aliases::person1.field(person::id))),
|
||||
),
|
||||
)
|
||||
.left_join(actions(
|
||||
person_actions::table,
|
||||
Some(aliases::person1.field(person::id)),
|
||||
private_message::creator_id,
|
||||
))
|
||||
.left_join(actions(
|
||||
instance_actions::table,
|
||||
Some(aliases::person1.field(person::id)),
|
||||
person::instance_id,
|
||||
))
|
||||
};
|
||||
|
||||
let selection = (
|
||||
|
@ -62,9 +58,9 @@ fn queries<'a>() -> Queries<
|
|||
let mut query = all_joins(private_message::table.into_boxed())
|
||||
.select(selection)
|
||||
// Dont show replies from blocked users
|
||||
.filter(person_block::person_id.is_null())
|
||||
.filter(person_actions::blocked.is_null())
|
||||
// Dont show replies from blocked instances
|
||||
.filter(instance_block::person_id.is_null());
|
||||
.filter(instance_actions::blocked.is_null());
|
||||
|
||||
// If its unread, I only want the ones to me
|
||||
if options.unread_only {
|
||||
|
@ -127,24 +123,20 @@ impl PrivateMessageView {
|
|||
private_message::table
|
||||
// Necessary to get the senders instance_id
|
||||
.inner_join(person::table.on(private_message::creator_id.eq(person::id)))
|
||||
.left_join(
|
||||
person_block::table.on(
|
||||
private_message::creator_id
|
||||
.eq(person_block::target_id)
|
||||
.and(person_block::person_id.eq(my_person_id)),
|
||||
),
|
||||
)
|
||||
.left_join(
|
||||
instance_block::table.on(
|
||||
person::instance_id
|
||||
.eq(instance_block::instance_id)
|
||||
.and(instance_block::person_id.eq(my_person_id)),
|
||||
),
|
||||
)
|
||||
.left_join(actions(
|
||||
person_actions::table,
|
||||
Some(my_person_id),
|
||||
private_message::creator_id,
|
||||
))
|
||||
.left_join(actions(
|
||||
instance_actions::table,
|
||||
Some(my_person_id),
|
||||
person::instance_id,
|
||||
))
|
||||
// Dont count replies from blocked users
|
||||
.filter(person_block::person_id.is_null())
|
||||
.filter(person_actions::blocked.is_null())
|
||||
// Dont count replies from blocked instances
|
||||
.filter(instance_block::person_id.is_null())
|
||||
.filter(instance_actions::blocked.is_null())
|
||||
.filter(private_message::read.eq(false))
|
||||
.filter(private_message::recipient_id.eq(my_person_id))
|
||||
.filter(private_message::deleted.eq(false))
|
||||
|
|
|
@ -1,17 +1,11 @@
|
|||
use crate::structs::VoteView;
|
||||
use diesel::{
|
||||
result::Error,
|
||||
BoolExpressionMethods,
|
||||
ExpressionMethods,
|
||||
JoinOnDsl,
|
||||
NullableExpressionMethods,
|
||||
QueryDsl,
|
||||
};
|
||||
use diesel::{result::Error, ExpressionMethods, NullableExpressionMethods, QueryDsl};
|
||||
use diesel_async::RunQueryDsl;
|
||||
use lemmy_db_schema::{
|
||||
aliases::creator_community_actions,
|
||||
newtypes::{CommentId, PostId},
|
||||
schema::{comment, comment_like, community_person_ban, person, post, post_like},
|
||||
utils::{get_conn, limit_and_offset, DbPool},
|
||||
schema::{comment, comment_actions, community_actions, person, post, post_actions},
|
||||
utils::{action_query, actions_alias, get_conn, limit_and_offset, DbPool},
|
||||
};
|
||||
|
||||
impl VoteView {
|
||||
|
@ -24,24 +18,24 @@ impl VoteView {
|
|||
let conn = &mut get_conn(pool).await?;
|
||||
let (limit, offset) = limit_and_offset(page, limit)?;
|
||||
|
||||
post_like::table
|
||||
action_query(post_actions::like_score)
|
||||
.inner_join(person::table)
|
||||
.inner_join(post::table)
|
||||
// Join to community_person_ban to get creator_banned_from_community
|
||||
.left_join(
|
||||
community_person_ban::table.on(
|
||||
post::community_id
|
||||
.eq(community_person_ban::community_id)
|
||||
.and(community_person_ban::person_id.eq(post_like::person_id)),
|
||||
),
|
||||
)
|
||||
.filter(post_like::post_id.eq(post_id))
|
||||
.left_join(actions_alias(
|
||||
creator_community_actions,
|
||||
post_actions::person_id,
|
||||
post::community_id,
|
||||
))
|
||||
.filter(post_actions::post_id.eq(post_id))
|
||||
.select((
|
||||
person::all_columns,
|
||||
community_person_ban::community_id.nullable().is_not_null(),
|
||||
post_like::score,
|
||||
creator_community_actions
|
||||
.field(community_actions::received_ban)
|
||||
.nullable()
|
||||
.is_not_null(),
|
||||
post_actions::like_score.assume_not_null(),
|
||||
))
|
||||
.order_by(post_like::score)
|
||||
.order_by(post_actions::like_score)
|
||||
.limit(limit)
|
||||
.offset(offset)
|
||||
.load::<Self>(conn)
|
||||
|
@ -57,25 +51,24 @@ impl VoteView {
|
|||
let conn = &mut get_conn(pool).await?;
|
||||
let (limit, offset) = limit_and_offset(page, limit)?;
|
||||
|
||||
comment_like::table
|
||||
action_query(comment_actions::like_score)
|
||||
.inner_join(person::table)
|
||||
.inner_join(comment::table)
|
||||
.inner_join(post::table.on(comment::post_id.eq(post::id)))
|
||||
// Join to community_person_ban to get creator_banned_from_community
|
||||
.left_join(
|
||||
community_person_ban::table.on(
|
||||
post::community_id
|
||||
.eq(community_person_ban::community_id)
|
||||
.and(community_person_ban::person_id.eq(comment_like::person_id)),
|
||||
),
|
||||
)
|
||||
.filter(comment_like::comment_id.eq(comment_id))
|
||||
.inner_join(comment::table.inner_join(post::table))
|
||||
.left_join(actions_alias(
|
||||
creator_community_actions,
|
||||
comment_actions::person_id,
|
||||
post::community_id,
|
||||
))
|
||||
.filter(comment_actions::comment_id.eq(comment_id))
|
||||
.select((
|
||||
person::all_columns,
|
||||
community_person_ban::community_id.nullable().is_not_null(),
|
||||
comment_like::score,
|
||||
creator_community_actions
|
||||
.field(community_actions::received_ban)
|
||||
.nullable()
|
||||
.is_not_null(),
|
||||
comment_actions::like_score.assume_not_null(),
|
||||
))
|
||||
.order_by(comment_like::score)
|
||||
.order_by(comment_actions::like_score)
|
||||
.limit(limit)
|
||||
.offset(offset)
|
||||
.load::<Self>(conn)
|
||||
|
|
|
@ -3,39 +3,40 @@ use diesel::{
|
|||
dsl::{exists, not},
|
||||
pg::Pg,
|
||||
result::Error,
|
||||
sql_types,
|
||||
BoolExpressionMethods,
|
||||
BoxableExpression,
|
||||
ExpressionMethods,
|
||||
IntoSql,
|
||||
JoinOnDsl,
|
||||
NullableExpressionMethods,
|
||||
QueryDsl,
|
||||
};
|
||||
use diesel_async::RunQueryDsl;
|
||||
use lemmy_db_schema::{
|
||||
aliases,
|
||||
aliases::{self, creator_community_actions},
|
||||
newtypes::{CommentReplyId, PersonId},
|
||||
schema::{
|
||||
comment,
|
||||
comment_actions,
|
||||
comment_aggregates,
|
||||
comment_like,
|
||||
comment_reply,
|
||||
comment_saved,
|
||||
community,
|
||||
community_follower,
|
||||
community_moderator,
|
||||
community_person_ban,
|
||||
community_actions,
|
||||
local_user,
|
||||
person,
|
||||
person_block,
|
||||
person_actions,
|
||||
post,
|
||||
},
|
||||
source::{
|
||||
community::{CommunityFollower, CommunityFollowerState},
|
||||
local_user::LocalUser,
|
||||
source::{community::CommunityFollower, local_user::LocalUser},
|
||||
utils::{
|
||||
actions,
|
||||
actions_alias,
|
||||
get_conn,
|
||||
limit_and_offset,
|
||||
DbConn,
|
||||
DbPool,
|
||||
ListFn,
|
||||
Queries,
|
||||
ReadFn,
|
||||
},
|
||||
utils::{get_conn, limit_and_offset, DbConn, DbPool, ListFn, Queries, ReadFn},
|
||||
CommentSortType,
|
||||
};
|
||||
|
||||
|
@ -43,74 +44,6 @@ fn queries<'a>() -> Queries<
|
|||
impl ReadFn<'a, CommentReplyView, (CommentReplyId, Option<PersonId>)>,
|
||||
impl ListFn<'a, CommentReplyView, CommentReplyQuery>,
|
||||
> {
|
||||
let is_creator_banned_from_community = exists(
|
||||
community_person_ban::table.filter(
|
||||
community::id
|
||||
.eq(community_person_ban::community_id)
|
||||
.and(community_person_ban::person_id.eq(comment::creator_id)),
|
||||
),
|
||||
);
|
||||
|
||||
let is_local_user_banned_from_community = |person_id| {
|
||||
exists(
|
||||
community_person_ban::table.filter(
|
||||
community::id
|
||||
.eq(community_person_ban::community_id)
|
||||
.and(community_person_ban::person_id.eq(person_id)),
|
||||
),
|
||||
)
|
||||
};
|
||||
|
||||
let is_saved = |person_id| {
|
||||
exists(
|
||||
comment_saved::table.filter(
|
||||
comment::id
|
||||
.eq(comment_saved::comment_id)
|
||||
.and(comment_saved::person_id.eq(person_id)),
|
||||
),
|
||||
)
|
||||
};
|
||||
|
||||
let is_community_followed = |person_id| {
|
||||
community_follower::table
|
||||
.filter(
|
||||
post::community_id
|
||||
.eq(community_follower::community_id)
|
||||
.and(community_follower::person_id.eq(person_id)),
|
||||
)
|
||||
.select(CommunityFollower::select_subscribed_type())
|
||||
.single_value()
|
||||
};
|
||||
|
||||
let is_creator_blocked = |person_id| {
|
||||
exists(
|
||||
person_block::table.filter(
|
||||
comment::creator_id
|
||||
.eq(person_block::target_id)
|
||||
.and(person_block::person_id.eq(person_id)),
|
||||
),
|
||||
)
|
||||
};
|
||||
|
||||
let score = |person_id| {
|
||||
comment_like::table
|
||||
.filter(
|
||||
comment::id
|
||||
.eq(comment_like::comment_id)
|
||||
.and(comment_like::person_id.eq(person_id)),
|
||||
)
|
||||
.select(comment_like::score.nullable())
|
||||
.single_value()
|
||||
};
|
||||
|
||||
let creator_is_moderator = exists(
|
||||
community_moderator::table.filter(
|
||||
community::id
|
||||
.eq(community_moderator::community_id)
|
||||
.and(community_moderator::person_id.eq(comment::creator_id)),
|
||||
),
|
||||
);
|
||||
|
||||
let creator_is_admin = exists(
|
||||
local_user::table.filter(
|
||||
comment::creator_id
|
||||
|
@ -121,48 +54,6 @@ fn queries<'a>() -> Queries<
|
|||
|
||||
let all_joins = move |query: comment_reply::BoxedQuery<'a, Pg>,
|
||||
my_person_id: Option<PersonId>| {
|
||||
let is_local_user_banned_from_community_selection: Box<
|
||||
dyn BoxableExpression<_, Pg, SqlType = sql_types::Bool>,
|
||||
> = if let Some(person_id) = my_person_id {
|
||||
Box::new(is_local_user_banned_from_community(person_id))
|
||||
} else {
|
||||
Box::new(false.into_sql::<sql_types::Bool>())
|
||||
};
|
||||
|
||||
let score_selection: Box<
|
||||
dyn BoxableExpression<_, Pg, SqlType = sql_types::Nullable<sql_types::SmallInt>>,
|
||||
> = if let Some(person_id) = my_person_id {
|
||||
Box::new(score(person_id))
|
||||
} else {
|
||||
Box::new(None::<i16>.into_sql::<sql_types::Nullable<sql_types::SmallInt>>())
|
||||
};
|
||||
|
||||
let subscribed_type_selection: Box<
|
||||
dyn BoxableExpression<
|
||||
_,
|
||||
Pg,
|
||||
SqlType = sql_types::Nullable<lemmy_db_schema::schema::sql_types::CommunityFollowerState>,
|
||||
>,
|
||||
> = if let Some(person_id) = my_person_id {
|
||||
Box::new(is_community_followed(person_id))
|
||||
} else {
|
||||
Box::new(None::<CommunityFollowerState>.into_sql::<sql_types::Nullable<lemmy_db_schema::schema::sql_types::CommunityFollowerState>>())
|
||||
};
|
||||
|
||||
let is_saved_selection: Box<dyn BoxableExpression<_, Pg, SqlType = sql_types::Bool>> =
|
||||
if let Some(person_id) = my_person_id {
|
||||
Box::new(is_saved(person_id))
|
||||
} else {
|
||||
Box::new(false.into_sql::<sql_types::Bool>())
|
||||
};
|
||||
|
||||
let is_creator_blocked_selection: Box<dyn BoxableExpression<_, Pg, SqlType = sql_types::Bool>> =
|
||||
if let Some(person_id) = my_person_id {
|
||||
Box::new(is_creator_blocked(person_id))
|
||||
} else {
|
||||
Box::new(false.into_sql::<sql_types::Bool>())
|
||||
};
|
||||
|
||||
query
|
||||
.inner_join(comment::table)
|
||||
.inner_join(person::table.on(comment::creator_id.eq(person::id)))
|
||||
|
@ -170,6 +61,22 @@ fn queries<'a>() -> Queries<
|
|||
.inner_join(community::table.on(post::community_id.eq(community::id)))
|
||||
.inner_join(aliases::person1)
|
||||
.inner_join(comment_aggregates::table.on(comment::id.eq(comment_aggregates::comment_id)))
|
||||
.left_join(actions(comment_actions::table, my_person_id, comment::id))
|
||||
.left_join(actions(
|
||||
community_actions::table,
|
||||
my_person_id,
|
||||
post::community_id,
|
||||
))
|
||||
.left_join(actions(
|
||||
person_actions::table,
|
||||
my_person_id,
|
||||
comment::creator_id,
|
||||
))
|
||||
.left_join(actions_alias(
|
||||
creator_community_actions,
|
||||
comment::creator_id,
|
||||
post::community_id,
|
||||
))
|
||||
.select((
|
||||
comment_reply::all_columns,
|
||||
comment::all_columns,
|
||||
|
@ -178,14 +85,20 @@ fn queries<'a>() -> Queries<
|
|||
community::all_columns,
|
||||
aliases::person1.fields(person::all_columns),
|
||||
comment_aggregates::all_columns,
|
||||
is_creator_banned_from_community,
|
||||
is_local_user_banned_from_community_selection,
|
||||
creator_is_moderator,
|
||||
creator_community_actions
|
||||
.field(community_actions::received_ban)
|
||||
.nullable()
|
||||
.is_not_null(),
|
||||
community_actions::received_ban.nullable().is_not_null(),
|
||||
creator_community_actions
|
||||
.field(community_actions::became_moderator)
|
||||
.nullable()
|
||||
.is_not_null(),
|
||||
creator_is_admin,
|
||||
subscribed_type_selection,
|
||||
is_saved_selection,
|
||||
is_creator_blocked_selection,
|
||||
score_selection,
|
||||
CommunityFollower::select_subscribed_type(),
|
||||
comment_actions::saved.nullable().is_not_null(),
|
||||
person_actions::blocked.nullable().is_not_null(),
|
||||
comment_actions::like_score.nullable(),
|
||||
))
|
||||
};
|
||||
|
||||
|
@ -228,9 +141,7 @@ fn queries<'a>() -> Queries<
|
|||
};
|
||||
|
||||
// Don't show replies from blocked persons
|
||||
if let Some(my_person_id) = options.my_person_id {
|
||||
query = query.filter(not(is_creator_blocked(my_person_id)));
|
||||
}
|
||||
query = query.filter(person_actions::blocked.is_null());
|
||||
|
||||
let (limit, offset) = limit_and_offset(options.page, options.limit)?;
|
||||
|
||||
|
@ -264,13 +175,11 @@ impl CommentReplyView {
|
|||
|
||||
let mut query = comment_reply::table
|
||||
.inner_join(comment::table)
|
||||
.left_join(
|
||||
person_block::table.on(
|
||||
comment::creator_id
|
||||
.eq(person_block::target_id)
|
||||
.and(person_block::person_id.eq(local_user.person_id)),
|
||||
),
|
||||
)
|
||||
.left_join(actions(
|
||||
person_actions::table,
|
||||
Some(local_user.person_id),
|
||||
comment::creator_id,
|
||||
))
|
||||
.inner_join(person::table.on(comment::creator_id.eq(person::id)))
|
||||
.into_boxed();
|
||||
|
||||
|
@ -281,7 +190,7 @@ impl CommentReplyView {
|
|||
|
||||
query
|
||||
// Don't count replies from blocked users
|
||||
.filter(person_block::person_id.is_null())
|
||||
.filter(person_actions::blocked.is_null())
|
||||
.filter(comment_reply::recipient_id.eq(local_user.person_id))
|
||||
.filter(comment_reply::read.eq(false))
|
||||
.filter(comment::deleted.eq(false))
|
||||
|
|
|
@ -12,12 +12,12 @@ use diesel::{
|
|||
use diesel_async::RunQueryDsl;
|
||||
use lemmy_db_schema::{
|
||||
newtypes::{CommunityId, DbUrl, InstanceId, PersonId},
|
||||
schema::{community, community_follower, community_moderator, person},
|
||||
schema::{community, community_actions, person},
|
||||
source::{
|
||||
community::{Community, CommunityFollower, CommunityFollowerState},
|
||||
person::Person,
|
||||
},
|
||||
utils::{get_conn, limit_and_offset, DbPool},
|
||||
utils::{action_query, get_conn, limit_and_offset, DbPool},
|
||||
CommunityVisibility,
|
||||
SubscribedType,
|
||||
};
|
||||
|
@ -39,14 +39,14 @@ impl CommunityFollowerView {
|
|||
// that would work for all instances that support fully shared inboxes.
|
||||
// It would be a bit more complicated though to keep it in sync.
|
||||
|
||||
community_follower::table
|
||||
community_actions::table
|
||||
.inner_join(community::table)
|
||||
.inner_join(person::table.on(community_follower::person_id.eq(person::id)))
|
||||
.inner_join(person::table.on(community_actions::person_id.eq(person::id)))
|
||||
.filter(person::instance_id.eq(instance_id))
|
||||
.filter(community::local) // this should be a no-op since community_followers table only has
|
||||
// local-person+remote-community or remote-person+local-community
|
||||
.filter(not(person::local))
|
||||
.filter(community_follower::published.gt(published_since.naive_utc()))
|
||||
.filter(community_actions::followed.gt(published_since.naive_utc()))
|
||||
.select((community::id, person::inbox_url))
|
||||
.distinct() // only need each community_id, inbox combination once
|
||||
.load::<(CommunityId, DbUrl)>(conn)
|
||||
|
@ -57,10 +57,10 @@ impl CommunityFollowerView {
|
|||
community_id: CommunityId,
|
||||
) -> Result<Vec<DbUrl>, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
let res = community_follower::table
|
||||
.filter(community_follower::community_id.eq(community_id))
|
||||
let res = action_query(community_actions::followed)
|
||||
.filter(community_actions::community_id.eq(community_id))
|
||||
.filter(not(person::local))
|
||||
.inner_join(person::table.on(community_follower::person_id.eq(person::id)))
|
||||
.inner_join(person::table.on(community_actions::person_id.eq(person::id)))
|
||||
.select(person::inbox_url)
|
||||
.distinct()
|
||||
.load::<DbUrl>(conn)
|
||||
|
@ -73,8 +73,8 @@ impl CommunityFollowerView {
|
|||
community_id: CommunityId,
|
||||
) -> Result<i64, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
let res = community_follower::table
|
||||
.filter(community_follower::community_id.eq(community_id))
|
||||
let res = action_query(community_actions::followed)
|
||||
.filter(community_actions::community_id.eq(community_id))
|
||||
.select(count_star())
|
||||
.first::<i64>(conn)
|
||||
.await?;
|
||||
|
@ -84,11 +84,11 @@ impl CommunityFollowerView {
|
|||
|
||||
pub async fn for_person(pool: &mut DbPool<'_>, person_id: PersonId) -> Result<Vec<Self>, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
community_follower::table
|
||||
action_query(community_actions::followed)
|
||||
.inner_join(community::table)
|
||||
.inner_join(person::table.on(community_follower::person_id.eq(person::id)))
|
||||
.inner_join(person::table.on(community_actions::person_id.eq(person::id)))
|
||||
.select((community::all_columns, person::all_columns))
|
||||
.filter(community_follower::person_id.eq(person_id))
|
||||
.filter(community_actions::person_id.eq(person_id))
|
||||
.filter(community::deleted.eq(false))
|
||||
.filter(community::removed.eq(false))
|
||||
.order_by(community::title)
|
||||
|
@ -110,7 +110,7 @@ impl CommunityFollowerView {
|
|||
let (limit, offset) = limit_and_offset(page, limit)?;
|
||||
let (person_alias, community_follower_alias) = diesel::alias!(
|
||||
person as person_alias,
|
||||
community_follower as community_follower_alias
|
||||
community_actions as community_follower_alias
|
||||
);
|
||||
|
||||
// check if the community already has an accepted follower from the same instance
|
||||
|
@ -120,7 +120,7 @@ impl CommunityFollowerView {
|
|||
community_follower_alias.on(
|
||||
person_alias
|
||||
.field(person::id)
|
||||
.eq(community_follower_alias.field(community_follower::person_id)),
|
||||
.eq(community_follower_alias.field(community_actions::person_id)),
|
||||
),
|
||||
)
|
||||
.filter(
|
||||
|
@ -128,36 +128,33 @@ impl CommunityFollowerView {
|
|||
.eq(person_alias.field(person::instance_id))
|
||||
.and(
|
||||
community_follower_alias
|
||||
.field(community_follower::community_id)
|
||||
.eq(community_follower::community_id),
|
||||
.field(community_actions::community_id)
|
||||
.eq(community_actions::community_id),
|
||||
)
|
||||
.and(
|
||||
community_follower_alias
|
||||
.field(community_follower::state)
|
||||
.field(community_actions::follow_state)
|
||||
.eq(CommunityFollowerState::Accepted),
|
||||
),
|
||||
),
|
||||
));
|
||||
|
||||
let mut query = community_follower::table
|
||||
.inner_join(person::table.on(community_follower::person_id.eq(person::id)))
|
||||
let mut query = action_query(community_actions::followed)
|
||||
.inner_join(person::table.on(community_actions::person_id.eq(person::id)))
|
||||
.inner_join(community::table)
|
||||
.into_boxed();
|
||||
if all_communities {
|
||||
// if param is false, only return items for communities where user is a mod
|
||||
query = query.filter(exists(
|
||||
community_moderator::table.filter(
|
||||
community_follower::community_id
|
||||
.eq(community_moderator::community_id)
|
||||
.and(community_moderator::person_id.eq(person_id)),
|
||||
),
|
||||
));
|
||||
query = query
|
||||
.filter(community_actions::became_moderator.is_not_null())
|
||||
.filter(community_actions::person_id.eq(person_id));
|
||||
}
|
||||
if pending_only {
|
||||
query = query.filter(community_follower::state.eq(CommunityFollowerState::ApprovalRequired));
|
||||
query =
|
||||
query.filter(community_actions::follow_state.eq(CommunityFollowerState::ApprovalRequired));
|
||||
}
|
||||
let res = query
|
||||
.order_by(community_follower::published.asc())
|
||||
.order_by(community_actions::followed.asc())
|
||||
.limit(limit)
|
||||
.offset(offset)
|
||||
.select((
|
||||
|
@ -188,11 +185,11 @@ impl CommunityFollowerView {
|
|||
community_id: CommunityId,
|
||||
) -> Result<i64, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
community_follower::table
|
||||
.inner_join(person::table.on(community_follower::person_id.eq(person::id)))
|
||||
.filter(community_follower::community_id.eq(community_id))
|
||||
.filter(community_follower::state.eq(CommunityFollowerState::ApprovalRequired))
|
||||
.select(count(community_follower::community_id))
|
||||
action_query(community_actions::followed)
|
||||
.inner_join(person::table.on(community_actions::person_id.eq(person::id)))
|
||||
.filter(community_actions::community_id.eq(community_id))
|
||||
.filter(community_actions::follow_state.eq(CommunityFollowerState::ApprovalRequired))
|
||||
.select(count(community_actions::community_id))
|
||||
.first::<i64>(conn)
|
||||
.await
|
||||
}
|
||||
|
@ -206,10 +203,10 @@ impl CommunityFollowerView {
|
|||
}
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
select(exists(
|
||||
community_follower::table
|
||||
.filter(community_follower::community_id.eq(community.id))
|
||||
.filter(community_follower::person_id.eq(from_person_id))
|
||||
.filter(community_follower::state.eq(CommunityFollowerState::Accepted)),
|
||||
action_query(community_actions::followed)
|
||||
.filter(community_actions::community_id.eq(community.id))
|
||||
.filter(community_actions::person_id.eq(from_person_id))
|
||||
.filter(community_actions::follow_state.eq(CommunityFollowerState::Accepted)),
|
||||
))
|
||||
.get_result::<bool>(conn)
|
||||
.await?
|
||||
|
@ -223,11 +220,11 @@ impl CommunityFollowerView {
|
|||
) -> Result<(), Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
select(exists(
|
||||
community_follower::table
|
||||
.inner_join(person::table.on(community_follower::person_id.eq(person::id)))
|
||||
.filter(community_follower::community_id.eq(community_id))
|
||||
action_query(community_actions::followed)
|
||||
.inner_join(person::table.on(community_actions::person_id.eq(person::id)))
|
||||
.filter(community_actions::community_id.eq(community_id))
|
||||
.filter(person::instance_id.eq(instance_id))
|
||||
.filter(community_follower::state.eq(CommunityFollowerState::Accepted)),
|
||||
.filter(community_actions::follow_state.eq(CommunityFollowerState::Accepted)),
|
||||
))
|
||||
.get_result::<bool>(conn)
|
||||
.await?
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
use crate::structs::CommunityModeratorView;
|
||||
use diesel::{dsl::exists, result::Error, select, ExpressionMethods, QueryDsl};
|
||||
use diesel::{dsl::exists, result::Error, select, ExpressionMethods, JoinOnDsl, QueryDsl};
|
||||
use diesel_async::RunQueryDsl;
|
||||
use lemmy_db_schema::{
|
||||
impls::local_user::LocalUserOptionHelper,
|
||||
newtypes::{CommunityId, PersonId},
|
||||
schema::{community, community_moderator, person},
|
||||
schema::{community, community_actions, person},
|
||||
source::local_user::LocalUser,
|
||||
utils::{get_conn, DbPool},
|
||||
utils::{action_query, find_action, get_conn, DbPool},
|
||||
};
|
||||
use lemmy_utils::error::{LemmyErrorType, LemmyResult};
|
||||
|
||||
|
@ -16,17 +16,11 @@ impl CommunityModeratorView {
|
|||
find_community_id: CommunityId,
|
||||
find_person_id: PersonId,
|
||||
) -> LemmyResult<()> {
|
||||
use lemmy_db_schema::schema::community_moderator::dsl::{
|
||||
community_id,
|
||||
community_moderator,
|
||||
person_id,
|
||||
};
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
select(exists(
|
||||
community_moderator
|
||||
.filter(community_id.eq(find_community_id))
|
||||
.filter(person_id.eq(find_person_id)),
|
||||
))
|
||||
select(exists(find_action(
|
||||
community_actions::became_moderator,
|
||||
(find_person_id, find_community_id),
|
||||
)))
|
||||
.get_result::<bool>(conn)
|
||||
.await?
|
||||
.then_some(())
|
||||
|
@ -37,10 +31,10 @@ impl CommunityModeratorView {
|
|||
pool: &mut DbPool<'_>,
|
||||
find_person_id: PersonId,
|
||||
) -> LemmyResult<()> {
|
||||
use lemmy_db_schema::schema::community_moderator::dsl::{community_moderator, person_id};
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
select(exists(
|
||||
community_moderator.filter(person_id.eq(find_person_id)),
|
||||
action_query(community_actions::became_moderator)
|
||||
.filter(community_actions::person_id.eq(find_person_id)),
|
||||
))
|
||||
.get_result::<bool>(conn)
|
||||
.await?
|
||||
|
@ -53,12 +47,12 @@ impl CommunityModeratorView {
|
|||
community_id: CommunityId,
|
||||
) -> Result<Vec<Self>, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
community_moderator::table
|
||||
action_query(community_actions::became_moderator)
|
||||
.inner_join(community::table)
|
||||
.inner_join(person::table)
|
||||
.filter(community_moderator::community_id.eq(community_id))
|
||||
.inner_join(person::table.on(person::id.eq(community_actions::person_id)))
|
||||
.filter(community_actions::community_id.eq(community_id))
|
||||
.select((community::all_columns, person::all_columns))
|
||||
.order_by(community_moderator::published)
|
||||
.order_by(community_actions::became_moderator)
|
||||
.load::<CommunityModeratorView>(conn)
|
||||
.await
|
||||
}
|
||||
|
@ -69,10 +63,10 @@ impl CommunityModeratorView {
|
|||
local_user: Option<&LocalUser>,
|
||||
) -> Result<Vec<Self>, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
let mut query = community_moderator::table
|
||||
let mut query = action_query(community_actions::became_moderator)
|
||||
.inner_join(community::table)
|
||||
.inner_join(person::table)
|
||||
.filter(community_moderator::person_id.eq(person_id))
|
||||
.inner_join(person::table.on(person::id.eq(community_actions::person_id)))
|
||||
.filter(community_actions::person_id.eq(person_id))
|
||||
.select((community::all_columns, person::all_columns))
|
||||
.into_boxed();
|
||||
|
||||
|
@ -95,16 +89,16 @@ impl CommunityModeratorView {
|
|||
/// Ideally this should be a group by, but diesel doesn't support it yet
|
||||
pub async fn get_community_first_mods(pool: &mut DbPool<'_>) -> Result<Vec<Self>, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
community_moderator::table
|
||||
action_query(community_actions::became_moderator)
|
||||
.inner_join(community::table)
|
||||
.inner_join(person::table)
|
||||
.inner_join(person::table.on(person::id.eq(community_actions::person_id)))
|
||||
.select((community::all_columns, person::all_columns))
|
||||
// A hacky workaround instead of group_bys
|
||||
// https://stackoverflow.com/questions/24042359/how-to-join-only-one-row-in-joined-table-with-postgres
|
||||
.distinct_on(community_moderator::community_id)
|
||||
.distinct_on(community_actions::community_id)
|
||||
.order_by((
|
||||
community_moderator::community_id,
|
||||
community_moderator::published,
|
||||
community_actions::community_id,
|
||||
community_actions::became_moderator,
|
||||
))
|
||||
.load::<CommunityModeratorView>(conn)
|
||||
.await
|
||||
|
|
|
@ -2,14 +2,12 @@ use crate::structs::CommunityPersonBanView;
|
|||
use diesel::{
|
||||
dsl::{exists, not},
|
||||
select,
|
||||
ExpressionMethods,
|
||||
QueryDsl,
|
||||
};
|
||||
use diesel_async::RunQueryDsl;
|
||||
use lemmy_db_schema::{
|
||||
newtypes::{CommunityId, PersonId},
|
||||
schema::community_person_ban,
|
||||
utils::{get_conn, DbPool},
|
||||
schema::community_actions,
|
||||
utils::{find_action, get_conn, DbPool},
|
||||
};
|
||||
use lemmy_utils::error::{LemmyErrorType, LemmyResult};
|
||||
|
||||
|
@ -20,11 +18,10 @@ impl CommunityPersonBanView {
|
|||
from_community_id: CommunityId,
|
||||
) -> LemmyResult<()> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
select(not(exists(
|
||||
community_person_ban::table
|
||||
.filter(community_person_ban::community_id.eq(from_community_id))
|
||||
.filter(community_person_ban::person_id.eq(from_person_id)),
|
||||
)))
|
||||
select(not(exists(find_action(
|
||||
community_actions::received_ban,
|
||||
(from_person_id, from_community_id),
|
||||
))))
|
||||
.get_result::<bool>(conn)
|
||||
.await?
|
||||
.then_some(())
|
||||
|
|
|
@ -4,7 +4,6 @@ use diesel::{
|
|||
result::Error,
|
||||
BoolExpressionMethods,
|
||||
ExpressionMethods,
|
||||
JoinOnDsl,
|
||||
NullableExpressionMethods,
|
||||
PgTextExpressionMethods,
|
||||
QueryDsl,
|
||||
|
@ -13,20 +12,14 @@ use diesel_async::RunQueryDsl;
|
|||
use lemmy_db_schema::{
|
||||
impls::local_user::LocalUserOptionHelper,
|
||||
newtypes::{CommunityId, PersonId},
|
||||
schema::{
|
||||
community,
|
||||
community_aggregates,
|
||||
community_block,
|
||||
community_follower,
|
||||
community_person_ban,
|
||||
instance_block,
|
||||
},
|
||||
schema::{community, community_actions, community_aggregates, instance_actions},
|
||||
source::{
|
||||
community::{CommunityFollower, CommunityFollowerState},
|
||||
local_user::LocalUser,
|
||||
site::Site,
|
||||
},
|
||||
utils::{
|
||||
actions,
|
||||
functions::lower,
|
||||
fuzzy_search,
|
||||
limit_and_offset,
|
||||
|
@ -46,47 +39,26 @@ fn queries<'a>() -> Queries<
|
|||
impl ListFn<'a, CommunityView, (CommunityQuery<'a>, &'a Site)>,
|
||||
> {
|
||||
let all_joins = |query: community::BoxedQuery<'a, Pg>, my_local_user: Option<&'a LocalUser>| {
|
||||
// The left join below will return None in this case
|
||||
let person_id_join = my_local_user.person_id().unwrap_or(PersonId(-1));
|
||||
|
||||
query
|
||||
.inner_join(community_aggregates::table)
|
||||
.left_join(
|
||||
community_follower::table.on(
|
||||
community::id
|
||||
.eq(community_follower::community_id)
|
||||
.and(community_follower::person_id.eq(person_id_join)),
|
||||
),
|
||||
)
|
||||
.left_join(
|
||||
instance_block::table.on(
|
||||
community::instance_id
|
||||
.eq(instance_block::instance_id)
|
||||
.and(instance_block::person_id.eq(person_id_join)),
|
||||
),
|
||||
)
|
||||
.left_join(
|
||||
community_block::table.on(
|
||||
community::id
|
||||
.eq(community_block::community_id)
|
||||
.and(community_block::person_id.eq(person_id_join)),
|
||||
),
|
||||
)
|
||||
.left_join(
|
||||
community_person_ban::table.on(
|
||||
community::id
|
||||
.eq(community_person_ban::community_id)
|
||||
.and(community_person_ban::person_id.eq(person_id_join)),
|
||||
),
|
||||
)
|
||||
.left_join(actions(
|
||||
community_actions::table,
|
||||
my_local_user.person_id(),
|
||||
community::id,
|
||||
))
|
||||
.left_join(actions(
|
||||
instance_actions::table,
|
||||
my_local_user.person_id(),
|
||||
community::instance_id,
|
||||
))
|
||||
};
|
||||
|
||||
let selection = (
|
||||
community::all_columns,
|
||||
CommunityFollower::select_subscribed_type(),
|
||||
community_block::community_id.nullable().is_not_null(),
|
||||
community_actions::blocked.nullable().is_not_null(),
|
||||
community_aggregates::all_columns,
|
||||
community_person_ban::person_id.nullable().is_not_null(),
|
||||
community_actions::received_ban.nullable().is_not_null(),
|
||||
);
|
||||
|
||||
let not_removed_or_deleted = community::removed
|
||||
|
@ -118,9 +90,6 @@ fn queries<'a>() -> Queries<
|
|||
let list = move |mut conn: DbConn<'a>, (options, site): (CommunityQuery<'a>, &'a Site)| async move {
|
||||
use CommunitySortType::*;
|
||||
|
||||
// The left join below will return None in this case
|
||||
let person_id_join = options.local_user.person_id().unwrap_or(PersonId(-1));
|
||||
|
||||
let mut query = all_joins(community::table.into_boxed(), options.local_user).select(selection);
|
||||
|
||||
if let Some(search_term) = options.search_term {
|
||||
|
@ -140,7 +109,7 @@ fn queries<'a>() -> Queries<
|
|||
query = query.filter(not_removed_or_deleted).filter(
|
||||
community::hidden
|
||||
.eq(false)
|
||||
.or(community_follower::person_id.eq(person_id_join)),
|
||||
.or(community_actions::follow_state.is_not_null()),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -168,7 +137,7 @@ fn queries<'a>() -> Queries<
|
|||
if let Some(listing_type) = options.listing_type {
|
||||
query = match listing_type {
|
||||
ListingType::Subscribed => {
|
||||
query.filter(community_follower::state.eq(CommunityFollowerState::Accepted))
|
||||
query.filter(community_actions::follow_state.eq(Some(CommunityFollowerState::Accepted)))
|
||||
}
|
||||
ListingType::Local => query.filter(community::local.eq(true)),
|
||||
_ => query,
|
||||
|
@ -177,8 +146,8 @@ fn queries<'a>() -> Queries<
|
|||
|
||||
// Don't show blocked communities and communities on blocked instances. nsfw communities are
|
||||
// also hidden (based on profile setting)
|
||||
query = query.filter(instance_block::person_id.is_null());
|
||||
query = query.filter(community_block::person_id.is_null());
|
||||
query = query.filter(instance_actions::blocked.is_null());
|
||||
query = query.filter(community_actions::blocked.is_null());
|
||||
if !(options.local_user.show_nsfw(site) || options.show_nsfw) {
|
||||
query = query.filter(community::nsfw.eq(false));
|
||||
}
|
||||
|
|
|
@ -3,39 +3,40 @@ use diesel::{
|
|||
dsl::{exists, not},
|
||||
pg::Pg,
|
||||
result::Error,
|
||||
sql_types,
|
||||
BoolExpressionMethods,
|
||||
BoxableExpression,
|
||||
ExpressionMethods,
|
||||
IntoSql,
|
||||
JoinOnDsl,
|
||||
NullableExpressionMethods,
|
||||
QueryDsl,
|
||||
};
|
||||
use diesel_async::RunQueryDsl;
|
||||
use lemmy_db_schema::{
|
||||
aliases,
|
||||
aliases::{self, creator_community_actions},
|
||||
newtypes::{PersonId, PersonMentionId},
|
||||
schema::{
|
||||
comment,
|
||||
comment_actions,
|
||||
comment_aggregates,
|
||||
comment_like,
|
||||
comment_saved,
|
||||
community,
|
||||
community_follower,
|
||||
community_moderator,
|
||||
community_person_ban,
|
||||
community_actions,
|
||||
local_user,
|
||||
person,
|
||||
person_block,
|
||||
person_actions,
|
||||
person_mention,
|
||||
post,
|
||||
},
|
||||
source::{
|
||||
community::{CommunityFollower, CommunityFollowerState},
|
||||
local_user::LocalUser,
|
||||
source::{community::CommunityFollower, local_user::LocalUser},
|
||||
utils::{
|
||||
actions,
|
||||
actions_alias,
|
||||
get_conn,
|
||||
limit_and_offset,
|
||||
DbConn,
|
||||
DbPool,
|
||||
ListFn,
|
||||
Queries,
|
||||
ReadFn,
|
||||
},
|
||||
utils::{get_conn, limit_and_offset, DbConn, DbPool, ListFn, Queries, ReadFn},
|
||||
CommentSortType,
|
||||
};
|
||||
|
||||
|
@ -43,74 +44,6 @@ fn queries<'a>() -> Queries<
|
|||
impl ReadFn<'a, PersonMentionView, (PersonMentionId, Option<PersonId>)>,
|
||||
impl ListFn<'a, PersonMentionView, PersonMentionQuery>,
|
||||
> {
|
||||
let is_creator_banned_from_community = exists(
|
||||
community_person_ban::table.filter(
|
||||
community::id
|
||||
.eq(community_person_ban::community_id)
|
||||
.and(community_person_ban::person_id.eq(comment::creator_id)),
|
||||
),
|
||||
);
|
||||
|
||||
let is_local_user_banned_from_community = |person_id| {
|
||||
exists(
|
||||
community_person_ban::table.filter(
|
||||
community::id
|
||||
.eq(community_person_ban::community_id)
|
||||
.and(community_person_ban::person_id.eq(person_id)),
|
||||
),
|
||||
)
|
||||
};
|
||||
|
||||
let is_saved = |person_id| {
|
||||
exists(
|
||||
comment_saved::table.filter(
|
||||
comment::id
|
||||
.eq(comment_saved::comment_id)
|
||||
.and(comment_saved::person_id.eq(person_id)),
|
||||
),
|
||||
)
|
||||
};
|
||||
|
||||
let is_community_followed = |person_id| {
|
||||
community_follower::table
|
||||
.filter(
|
||||
post::community_id
|
||||
.eq(community_follower::community_id)
|
||||
.and(community_follower::person_id.eq(person_id)),
|
||||
)
|
||||
.select(CommunityFollower::select_subscribed_type())
|
||||
.single_value()
|
||||
};
|
||||
|
||||
let is_creator_blocked = |person_id| {
|
||||
exists(
|
||||
person_block::table.filter(
|
||||
comment::creator_id
|
||||
.eq(person_block::target_id)
|
||||
.and(person_block::person_id.eq(person_id)),
|
||||
),
|
||||
)
|
||||
};
|
||||
|
||||
let score = |person_id| {
|
||||
comment_like::table
|
||||
.filter(
|
||||
comment::id
|
||||
.eq(comment_like::comment_id)
|
||||
.and(comment_like::person_id.eq(person_id)),
|
||||
)
|
||||
.select(comment_like::score.nullable())
|
||||
.single_value()
|
||||
};
|
||||
|
||||
let creator_is_moderator = exists(
|
||||
community_moderator::table.filter(
|
||||
community::id
|
||||
.eq(community_moderator::community_id)
|
||||
.and(community_moderator::person_id.eq(comment::creator_id)),
|
||||
),
|
||||
);
|
||||
|
||||
let creator_is_admin = exists(
|
||||
local_user::table.filter(
|
||||
comment::creator_id
|
||||
|
@ -121,47 +54,6 @@ fn queries<'a>() -> Queries<
|
|||
|
||||
let all_joins = move |query: person_mention::BoxedQuery<'a, Pg>,
|
||||
my_person_id: Option<PersonId>| {
|
||||
let is_local_user_banned_from_community_selection: Box<
|
||||
dyn BoxableExpression<_, Pg, SqlType = sql_types::Bool>,
|
||||
> = if let Some(person_id) = my_person_id {
|
||||
Box::new(is_local_user_banned_from_community(person_id))
|
||||
} else {
|
||||
Box::new(false.into_sql::<sql_types::Bool>())
|
||||
};
|
||||
let score_selection: Box<
|
||||
dyn BoxableExpression<_, Pg, SqlType = sql_types::Nullable<sql_types::SmallInt>>,
|
||||
> = if let Some(person_id) = my_person_id {
|
||||
Box::new(score(person_id))
|
||||
} else {
|
||||
Box::new(None::<i16>.into_sql::<sql_types::Nullable<sql_types::SmallInt>>())
|
||||
};
|
||||
|
||||
let subscribed_type_selection: Box<
|
||||
dyn BoxableExpression<
|
||||
_,
|
||||
Pg,
|
||||
SqlType = sql_types::Nullable<lemmy_db_schema::schema::sql_types::CommunityFollowerState>,
|
||||
>,
|
||||
> = if let Some(person_id) = my_person_id {
|
||||
Box::new(is_community_followed(person_id))
|
||||
} else {
|
||||
Box::new(None::<CommunityFollowerState>.into_sql::<sql_types::Nullable<lemmy_db_schema::schema::sql_types::CommunityFollowerState>>())
|
||||
};
|
||||
|
||||
let is_saved_selection: Box<dyn BoxableExpression<_, Pg, SqlType = sql_types::Bool>> =
|
||||
if let Some(person_id) = my_person_id {
|
||||
Box::new(is_saved(person_id))
|
||||
} else {
|
||||
Box::new(false.into_sql::<sql_types::Bool>())
|
||||
};
|
||||
|
||||
let is_creator_blocked_selection: Box<dyn BoxableExpression<_, Pg, SqlType = sql_types::Bool>> =
|
||||
if let Some(person_id) = my_person_id {
|
||||
Box::new(is_creator_blocked(person_id))
|
||||
} else {
|
||||
Box::new(false.into_sql::<sql_types::Bool>())
|
||||
};
|
||||
|
||||
query
|
||||
.inner_join(comment::table)
|
||||
.inner_join(person::table.on(comment::creator_id.eq(person::id)))
|
||||
|
@ -169,6 +61,22 @@ fn queries<'a>() -> Queries<
|
|||
.inner_join(community::table.on(post::community_id.eq(community::id)))
|
||||
.inner_join(aliases::person1)
|
||||
.inner_join(comment_aggregates::table.on(comment::id.eq(comment_aggregates::comment_id)))
|
||||
.left_join(actions(
|
||||
community_actions::table,
|
||||
my_person_id,
|
||||
post::community_id,
|
||||
))
|
||||
.left_join(actions(comment_actions::table, my_person_id, comment::id))
|
||||
.left_join(actions(
|
||||
person_actions::table,
|
||||
my_person_id,
|
||||
comment::creator_id,
|
||||
))
|
||||
.left_join(actions_alias(
|
||||
creator_community_actions,
|
||||
comment::creator_id,
|
||||
post::community_id,
|
||||
))
|
||||
.select((
|
||||
person_mention::all_columns,
|
||||
comment::all_columns,
|
||||
|
@ -177,14 +85,20 @@ fn queries<'a>() -> Queries<
|
|||
community::all_columns,
|
||||
aliases::person1.fields(person::all_columns),
|
||||
comment_aggregates::all_columns,
|
||||
is_creator_banned_from_community,
|
||||
is_local_user_banned_from_community_selection,
|
||||
creator_is_moderator,
|
||||
creator_community_actions
|
||||
.field(community_actions::received_ban)
|
||||
.nullable()
|
||||
.is_not_null(),
|
||||
community_actions::received_ban.nullable().is_not_null(),
|
||||
creator_community_actions
|
||||
.field(community_actions::became_moderator)
|
||||
.nullable()
|
||||
.is_not_null(),
|
||||
creator_is_admin,
|
||||
subscribed_type_selection,
|
||||
is_saved_selection,
|
||||
is_creator_blocked_selection,
|
||||
score_selection,
|
||||
CommunityFollower::select_subscribed_type(),
|
||||
comment_actions::saved.nullable().is_not_null(),
|
||||
person_actions::blocked.nullable().is_not_null(),
|
||||
comment_actions::like_score.nullable(),
|
||||
))
|
||||
};
|
||||
|
||||
|
@ -227,9 +141,7 @@ fn queries<'a>() -> Queries<
|
|||
};
|
||||
|
||||
// Don't show mentions from blocked persons
|
||||
if let Some(my_person_id) = options.my_person_id {
|
||||
query = query.filter(not(is_creator_blocked(my_person_id)));
|
||||
}
|
||||
query = query.filter(person_actions::blocked.is_null());
|
||||
|
||||
let (limit, offset) = limit_and_offset(options.page, options.limit)?;
|
||||
|
||||
|
@ -264,13 +176,11 @@ impl PersonMentionView {
|
|||
|
||||
let mut query = person_mention::table
|
||||
.inner_join(comment::table)
|
||||
.left_join(
|
||||
person_block::table.on(
|
||||
comment::creator_id
|
||||
.eq(person_block::target_id)
|
||||
.and(person_block::person_id.eq(local_user.person_id)),
|
||||
),
|
||||
)
|
||||
.left_join(actions(
|
||||
person_actions::table,
|
||||
Some(local_user.person_id),
|
||||
comment::creator_id,
|
||||
))
|
||||
.inner_join(person::table.on(comment::creator_id.eq(person::id)))
|
||||
.into_boxed();
|
||||
|
||||
|
@ -281,7 +191,7 @@ impl PersonMentionView {
|
|||
|
||||
query
|
||||
// Don't count replies from blocked users
|
||||
.filter(person_block::person_id.is_null())
|
||||
.filter(person_actions::blocked.is_null())
|
||||
.filter(person_mention::recipient_id.eq(local_user.person_id))
|
||||
.filter(person_mention::read.eq(false))
|
||||
.filter(comment::deleted.eq(false))
|
||||
|
|
|
@ -85,26 +85,19 @@ fn has_newline(name: &str) -> bool {
|
|||
}
|
||||
|
||||
pub fn is_valid_actor_name(name: &str, actor_name_max_length: usize) -> LemmyResult<()> {
|
||||
static VALID_ACTOR_NAME_REGEX_EN: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"^[a-zA-Z0-9_]{3,}$").expect("compile regex"));
|
||||
static VALID_ACTOR_NAME_REGEX_AR: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"^[\p{Arabic}0-9_]{3,}$").expect("compile regex"));
|
||||
static VALID_ACTOR_NAME_REGEX_RU: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"^[\p{Cyrillic}0-9_]{3,}$").expect("compile regex"));
|
||||
|
||||
let check = name.chars().count() <= actor_name_max_length && !has_newline(name);
|
||||
|
||||
// Only allow characters from a single alphabet per username. This avoids problems with lookalike
|
||||
// characters like `o` which looks identical in Latin and Cyrillic, and can be used to imitate
|
||||
// other users. Checks for additional alphabets can be added in the same way.
|
||||
let lang_check = VALID_ACTOR_NAME_REGEX_EN.is_match(name)
|
||||
|| VALID_ACTOR_NAME_REGEX_AR.is_match(name)
|
||||
|| VALID_ACTOR_NAME_REGEX_RU.is_match(name);
|
||||
static VALID_ACTOR_NAME_REGEX: LazyLock<Regex> = LazyLock::new(|| {
|
||||
Regex::new(r"^(?:[a-zA-Z0-9_]+|[0-9_\p{Arabic}]+|[0-9_\p{Cyrillic}]+)$").expect("compile regex")
|
||||
});
|
||||
|
||||
if !check || !lang_check {
|
||||
Err(LemmyErrorType::InvalidName.into())
|
||||
} else {
|
||||
min_length_check(name, 3, LemmyErrorType::InvalidName)?;
|
||||
max_length_check(name, actor_name_max_length, LemmyErrorType::InvalidName)?;
|
||||
if VALID_ACTOR_NAME_REGEX.is_match(name) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(LemmyErrorType::InvalidName.into())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -379,11 +372,14 @@ mod tests {
|
|||
use pretty_assertions::assert_eq;
|
||||
use url::Url;
|
||||
|
||||
const URL_WITH_TRACKING: &str = "https://example.com/path/123?utm_content=buffercf3b2&utm_medium=social&user+name=random+user&id=123";
|
||||
const URL_TRACKING_REMOVED: &str = "https://example.com/path/123?user+name=random+user&id=123";
|
||||
|
||||
#[test]
|
||||
fn test_clean_url_params() -> LemmyResult<()> {
|
||||
let url = Url::parse("https://example.com/path/123?utm_content=buffercf3b2&utm_medium=social&user+name=random+user&id=123")?;
|
||||
let url = Url::parse(URL_WITH_TRACKING)?;
|
||||
let cleaned = clean_url(&url);
|
||||
let expected = Url::parse("https://example.com/path/123?user+name=random+user&id=123")?;
|
||||
let expected = Url::parse(URL_TRACKING_REMOVED)?;
|
||||
assert_eq!(expected.to_string(), cleaned.to_string());
|
||||
|
||||
let url = Url::parse("https://example.com/path/123")?;
|
||||
|
@ -395,9 +391,9 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_clean_body() -> LemmyResult<()> {
|
||||
let text = "[a link](https://example.com/path/123?utm_content=buffercf3b2&utm_medium=social&user+name=random+user&id=123)";
|
||||
let cleaned = clean_urls_in_text(text);
|
||||
let expected = "[a link](https://example.com/path/123?user+name=random+user&id=123)";
|
||||
let text = format!("[a link]({URL_WITH_TRACKING})");
|
||||
let cleaned = clean_urls_in_text(&text);
|
||||
let expected = format!("[a link]({URL_TRACKING_REMOVED})");
|
||||
assert_eq!(expected.to_string(), cleaned.to_string());
|
||||
|
||||
let text = "[a link](https://example.com/path/123)";
|
||||
|
@ -438,6 +434,15 @@ mod tests {
|
|||
assert!(is_valid_actor_name("a", actor_name_max_length).is_err());
|
||||
// empty
|
||||
assert!(is_valid_actor_name("", actor_name_max_length).is_err());
|
||||
// newline
|
||||
assert!(is_valid_actor_name(
|
||||
r"Line1
|
||||
|
||||
Line3",
|
||||
actor_name_max_length
|
||||
)
|
||||
.is_err());
|
||||
assert!(is_valid_actor_name("Line1\nLine3", actor_name_max_length).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
320
migrations/2024-11-10-134311_smoosh-tables-together/down.sql
Normal file
320
migrations/2024-11-10-134311_smoosh-tables-together/down.sql
Normal file
|
@ -0,0 +1,320 @@
|
|||
-- For each new actions table, create tables that are dropped in up.sql, and insert into them
|
||||
CREATE TABLE comment_saved (
|
||||
person_id int REFERENCES person ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
|
||||
comment_id int REFERENCES COMMENT ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
|
||||
published timestamptz DEFAULT now() NOT NULL,
|
||||
PRIMARY KEY (person_id, comment_id)
|
||||
);
|
||||
|
||||
INSERT INTO comment_saved (person_id, comment_id, published)
|
||||
SELECT
|
||||
person_id,
|
||||
comment_id,
|
||||
saved
|
||||
FROM
|
||||
comment_actions
|
||||
WHERE
|
||||
saved IS NOT NULL;
|
||||
|
||||
CREATE TABLE community_block (
|
||||
person_id int REFERENCES person ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
|
||||
community_id int REFERENCES community ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
|
||||
published timestamptz DEFAULT now() NOT NULL,
|
||||
PRIMARY KEY (person_id, community_id)
|
||||
);
|
||||
|
||||
INSERT INTO community_block (person_id, community_id, published)
|
||||
SELECT
|
||||
person_id,
|
||||
community_id,
|
||||
blocked
|
||||
FROM
|
||||
community_actions
|
||||
WHERE
|
||||
blocked IS NOT NULL;
|
||||
|
||||
CREATE TABLE community_person_ban (
|
||||
community_id int REFERENCES community ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
|
||||
person_id int REFERENCES person ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
|
||||
published timestamptz DEFAULT now() NOT NULL,
|
||||
expires timestamptz,
|
||||
PRIMARY KEY (person_id, community_id)
|
||||
);
|
||||
|
||||
INSERT INTO community_person_ban (community_id, person_id, published, expires)
|
||||
SELECT
|
||||
community_id,
|
||||
person_id,
|
||||
received_ban,
|
||||
ban_expires
|
||||
FROM
|
||||
community_actions
|
||||
WHERE
|
||||
received_ban IS NOT NULL;
|
||||
|
||||
CREATE TABLE community_moderator (
|
||||
community_id int REFERENCES community ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
|
||||
person_id int REFERENCES person ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
|
||||
published timestamptz DEFAULT now() NOT NULL,
|
||||
PRIMARY KEY (person_id, community_id)
|
||||
);
|
||||
|
||||
INSERT INTO community_moderator (community_id, person_id, published)
|
||||
SELECT
|
||||
community_id,
|
||||
person_id,
|
||||
became_moderator
|
||||
FROM
|
||||
community_actions
|
||||
WHERE
|
||||
became_moderator IS NOT NULL;
|
||||
|
||||
CREATE TABLE person_block (
|
||||
person_id int REFERENCES person ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
|
||||
target_id int REFERENCES person ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
|
||||
published timestamptz DEFAULT now() NOT NULL,
|
||||
PRIMARY KEY (person_id, target_id)
|
||||
);
|
||||
|
||||
INSERT INTO person_block (person_id, target_id, published)
|
||||
SELECT
|
||||
person_id,
|
||||
target_id,
|
||||
blocked
|
||||
FROM
|
||||
person_actions
|
||||
WHERE
|
||||
blocked IS NOT NULL;
|
||||
|
||||
CREATE TABLE person_post_aggregates (
|
||||
person_id int REFERENCES person ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
|
||||
post_id int REFERENCES post ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
|
||||
read_comments bigint DEFAULT 0 NOT NULL,
|
||||
published timestamptz NOT NULL,
|
||||
PRIMARY KEY (person_id, post_id)
|
||||
);
|
||||
|
||||
INSERT INTO person_post_aggregates (person_id, post_id, read_comments, published)
|
||||
SELECT
|
||||
person_id,
|
||||
post_id,
|
||||
read_comments_amount,
|
||||
read_comments
|
||||
FROM
|
||||
post_actions
|
||||
WHERE
|
||||
read_comments IS NOT NULL;
|
||||
|
||||
CREATE TABLE post_hide (
|
||||
post_id int REFERENCES post ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
|
||||
person_id int REFERENCES person ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
|
||||
published timestamptz DEFAULT now() NOT NULL,
|
||||
PRIMARY KEY (person_id, post_id)
|
||||
);
|
||||
|
||||
INSERT INTO post_hide (post_id, person_id, published)
|
||||
SELECT
|
||||
post_id,
|
||||
person_id,
|
||||
hidden
|
||||
FROM
|
||||
post_actions
|
||||
WHERE
|
||||
hidden IS NOT NULL;
|
||||
|
||||
CREATE TABLE post_like (
|
||||
post_id int REFERENCES post ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
|
||||
person_id int REFERENCES person ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
|
||||
score smallint NOT NULL,
|
||||
published timestamptz DEFAULT now() NOT NULL,
|
||||
PRIMARY KEY (person_id, post_id)
|
||||
);
|
||||
|
||||
INSERT INTO post_like (post_id, person_id, score, published)
|
||||
SELECT
|
||||
post_id,
|
||||
person_id,
|
||||
like_score,
|
||||
liked
|
||||
FROM
|
||||
post_actions
|
||||
WHERE
|
||||
liked IS NOT NULL;
|
||||
|
||||
CREATE TABLE post_saved (
|
||||
post_id int REFERENCES post ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
|
||||
person_id int REFERENCES person ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
|
||||
published timestamptz DEFAULT now() NOT NULL,
|
||||
PRIMARY KEY (person_id, post_id)
|
||||
);
|
||||
|
||||
INSERT INTO post_saved (post_id, person_id, published)
|
||||
SELECT
|
||||
post_id,
|
||||
person_id,
|
||||
saved
|
||||
FROM
|
||||
post_actions
|
||||
WHERE
|
||||
saved IS NOT NULL;
|
||||
|
||||
-- Do the opposite of the `ALTER TABLE` commands in up.sql
|
||||
DELETE FROM comment_actions
|
||||
WHERE liked IS NULL;
|
||||
|
||||
DELETE FROM community_actions
|
||||
WHERE followed IS NULL;
|
||||
|
||||
DELETE FROM instance_actions
|
||||
WHERE blocked IS NULL;
|
||||
|
||||
DELETE FROM person_actions
|
||||
WHERE followed IS NULL;
|
||||
|
||||
DELETE FROM post_actions
|
||||
WHERE read IS NULL;
|
||||
|
||||
ALTER TABLE comment_actions RENAME TO comment_like;
|
||||
|
||||
ALTER TABLE community_actions RENAME TO community_follower;
|
||||
|
||||
ALTER TABLE instance_actions RENAME TO instance_block;
|
||||
|
||||
ALTER TABLE person_actions RENAME TO person_follower;
|
||||
|
||||
ALTER TABLE post_actions RENAME TO post_read;
|
||||
|
||||
ALTER TABLE comment_like RENAME COLUMN liked TO published;
|
||||
|
||||
ALTER TABLE comment_like RENAME COLUMN like_score TO score;
|
||||
|
||||
ALTER TABLE community_follower RENAME COLUMN followed TO published;
|
||||
|
||||
ALTER TABLE community_follower RENAME COLUMN follow_state TO state;
|
||||
|
||||
ALTER TABLE community_follower RENAME COLUMN follow_approver_id TO approver_id;
|
||||
|
||||
ALTER TABLE instance_block RENAME COLUMN blocked TO published;
|
||||
|
||||
ALTER TABLE person_follower RENAME COLUMN person_id TO follower_id;
|
||||
|
||||
ALTER TABLE person_follower RENAME COLUMN target_id TO person_id;
|
||||
|
||||
ALTER TABLE person_follower RENAME COLUMN followed TO published;
|
||||
|
||||
ALTER TABLE person_follower RENAME COLUMN follow_pending TO pending;
|
||||
|
||||
ALTER TABLE post_read RENAME COLUMN read TO published;
|
||||
|
||||
ALTER TABLE comment_like
|
||||
DROP CONSTRAINT comment_actions_check_liked,
|
||||
ALTER COLUMN published SET NOT NULL,
|
||||
ALTER COLUMN published SET DEFAULT now(),
|
||||
ALTER COLUMN score SET NOT NULL,
|
||||
DROP COLUMN saved;
|
||||
|
||||
ALTER TABLE community_follower
|
||||
DROP CONSTRAINT community_actions_check_followed,
|
||||
DROP CONSTRAINT community_actions_check_received_ban,
|
||||
ALTER COLUMN published SET NOT NULL,
|
||||
ALTER COLUMN published SET DEFAULT now(),
|
||||
ALTER COLUMN state SET NOT NULL,
|
||||
DROP COLUMN blocked,
|
||||
DROP COLUMN became_moderator,
|
||||
DROP COLUMN received_ban,
|
||||
DROP COLUMN ban_expires;
|
||||
|
||||
ALTER TABLE instance_block
|
||||
ALTER COLUMN published SET NOT NULL,
|
||||
ALTER COLUMN published SET DEFAULT now();
|
||||
|
||||
ALTER TABLE person_follower
|
||||
DROP CONSTRAINT person_actions_check_followed,
|
||||
ALTER COLUMN published SET NOT NULL,
|
||||
ALTER COLUMN published SET DEFAULT now(),
|
||||
ALTER COLUMN pending SET NOT NULL,
|
||||
DROP COLUMN blocked;
|
||||
|
||||
ALTER TABLE post_read
|
||||
DROP CONSTRAINT post_actions_check_read_comments,
|
||||
DROP CONSTRAINT post_actions_check_liked,
|
||||
ALTER COLUMN published SET NOT NULL,
|
||||
ALTER COLUMN published SET DEFAULT now(),
|
||||
DROP COLUMN read_comments,
|
||||
DROP COLUMN read_comments_amount,
|
||||
DROP COLUMN saved,
|
||||
DROP COLUMN liked,
|
||||
DROP COLUMN like_score,
|
||||
DROP COLUMN hidden;
|
||||
|
||||
-- Rename associated stuff
|
||||
ALTER INDEX comment_actions_pkey RENAME TO comment_like_pkey;
|
||||
|
||||
ALTER INDEX idx_comment_actions_comment RENAME TO idx_comment_like_comment;
|
||||
|
||||
ALTER TABLE comment_like RENAME CONSTRAINT comment_actions_comment_id_fkey TO comment_like_comment_id_fkey;
|
||||
|
||||
ALTER TABLE comment_like RENAME CONSTRAINT comment_actions_person_id_fkey TO comment_like_person_id_fkey;
|
||||
|
||||
ALTER INDEX community_actions_pkey RENAME TO community_follower_pkey;
|
||||
|
||||
ALTER INDEX idx_community_actions_community RENAME TO idx_community_follower_community;
|
||||
|
||||
ALTER TABLE community_follower RENAME CONSTRAINT community_actions_community_id_fkey TO community_follower_community_id_fkey;
|
||||
|
||||
ALTER TABLE community_follower RENAME CONSTRAINT community_actions_person_id_fkey TO community_follower_person_id_fkey;
|
||||
|
||||
ALTER TABLE community_follower RENAME CONSTRAINT community_actions_follow_approver_id_fkey TO community_follower_approver_id_fkey;
|
||||
|
||||
ALTER INDEX instance_actions_pkey RENAME TO instance_block_pkey;
|
||||
|
||||
ALTER TABLE instance_block RENAME CONSTRAINT instance_actions_instance_id_fkey TO instance_block_instance_id_fkey;
|
||||
|
||||
ALTER TABLE instance_block RENAME CONSTRAINT instance_actions_person_id_fkey TO instance_block_person_id_fkey;
|
||||
|
||||
ALTER INDEX person_actions_pkey RENAME TO person_follower_pkey;
|
||||
|
||||
ALTER TABLE person_follower RENAME CONSTRAINT person_actions_target_id_fkey TO person_follower_person_id_fkey;
|
||||
|
||||
ALTER TABLE person_follower RENAME CONSTRAINT person_actions_person_id_fkey TO person_follower_follower_id_fkey;
|
||||
|
||||
ALTER INDEX post_actions_pkey RENAME TO post_read_pkey;
|
||||
|
||||
ALTER TABLE post_read RENAME CONSTRAINT post_actions_person_id_fkey TO post_read_person_id_fkey;
|
||||
|
||||
ALTER TABLE post_read RENAME CONSTRAINT post_actions_post_id_fkey TO post_read_post_id_fkey;
|
||||
|
||||
-- Rename idx_community_actions_followed and remove filter
|
||||
CREATE INDEX idx_community_follower_published ON community_follower (published);
|
||||
|
||||
DROP INDEX idx_community_actions_followed;
|
||||
|
||||
-- Move indexes back to their original tables
|
||||
CREATE INDEX idx_comment_saved_comment ON comment_saved (comment_id);
|
||||
|
||||
CREATE INDEX idx_comment_saved_person ON comment_saved (person_id);
|
||||
|
||||
CREATE INDEX idx_community_block_community ON community_block (community_id);
|
||||
|
||||
CREATE INDEX idx_community_moderator_community ON community_moderator (community_id);
|
||||
|
||||
CREATE INDEX idx_community_moderator_published ON community_moderator (published);
|
||||
|
||||
CREATE INDEX idx_person_block_person ON person_block (person_id);
|
||||
|
||||
CREATE INDEX idx_person_block_target ON person_block (target_id);
|
||||
|
||||
CREATE INDEX idx_person_post_aggregates_person ON person_post_aggregates (person_id);
|
||||
|
||||
CREATE INDEX idx_person_post_aggregates_post ON person_post_aggregates (post_id);
|
||||
|
||||
CREATE INDEX idx_post_like_post ON post_like (post_id);
|
||||
|
||||
DROP INDEX idx_person_actions_person, idx_person_actions_target, idx_post_actions_person, idx_post_actions_post;
|
||||
|
||||
-- Drop `NOT NULL` indexes of columns that still exist
|
||||
DROP INDEX idx_comment_actions_liked_not_null, idx_community_actions_followed_not_null, idx_person_actions_followed_not_null, idx_post_actions_read_not_null, idx_instance_actions_blocked_not_null;
|
||||
|
||||
-- Drop statistics of columns that still exist
|
||||
DROP statistics comment_actions_liked_stat, community_actions_followed_stat, person_actions_followed_stat;
|
||||
|
338
migrations/2024-11-10-134311_smoosh-tables-together/up.sql
Normal file
338
migrations/2024-11-10-134311_smoosh-tables-together/up.sql
Normal file
|
@ -0,0 +1,338 @@
|
|||
-- For each new actions table, transform the table previously used for the most common action type
|
||||
-- into the new actions table, which should only change the table's metadata instead of rewriting the
|
||||
-- rows
|
||||
ALTER TABLE comment_like RENAME TO comment_actions;
|
||||
|
||||
ALTER TABLE community_follower RENAME TO community_actions;
|
||||
|
||||
ALTER TABLE instance_block RENAME TO instance_actions;
|
||||
|
||||
ALTER TABLE person_follower RENAME TO person_actions;
|
||||
|
||||
ALTER TABLE post_read RENAME TO post_actions;
|
||||
|
||||
ALTER TABLE comment_actions RENAME COLUMN published TO liked;
|
||||
|
||||
ALTER TABLE comment_actions RENAME COLUMN score TO like_score;
|
||||
|
||||
ALTER TABLE community_actions RENAME COLUMN published TO followed;
|
||||
|
||||
ALTER TABLE community_actions RENAME COLUMN state TO follow_state;
|
||||
|
||||
ALTER TABLE community_actions RENAME COLUMN approver_id TO follow_approver_id;
|
||||
|
||||
ALTER TABLE instance_actions RENAME COLUMN published TO blocked;
|
||||
|
||||
ALTER TABLE person_actions RENAME COLUMN person_id TO target_id;
|
||||
|
||||
ALTER TABLE person_actions RENAME COLUMN follower_id TO person_id;
|
||||
|
||||
ALTER TABLE person_actions RENAME COLUMN published TO followed;
|
||||
|
||||
ALTER TABLE person_actions RENAME COLUMN pending TO follow_pending;
|
||||
|
||||
ALTER TABLE post_actions RENAME COLUMN published TO read;
|
||||
|
||||
ALTER TABLE comment_actions
|
||||
ALTER COLUMN liked DROP NOT NULL,
|
||||
ALTER COLUMN liked DROP DEFAULT,
|
||||
ALTER COLUMN like_score DROP NOT NULL,
|
||||
ADD COLUMN saved timestamptz,
|
||||
ADD CONSTRAINT comment_actions_check_liked CHECK ((liked IS NULL) = (like_score IS NULL));
|
||||
|
||||
ALTER TABLE community_actions
|
||||
ALTER COLUMN followed DROP NOT NULL,
|
||||
ALTER COLUMN followed DROP DEFAULT,
|
||||
ALTER COLUMN follow_state DROP NOT NULL,
|
||||
ADD COLUMN blocked timestamptz,
|
||||
ADD COLUMN became_moderator timestamptz,
|
||||
ADD COLUMN received_ban timestamptz,
|
||||
ADD COLUMN ban_expires timestamptz,
|
||||
ADD CONSTRAINT community_actions_check_followed CHECK ((followed IS NULL) = (follow_state IS NULL) AND NOT (followed IS NULL AND follow_approver_id IS NOT NULL)),
|
||||
ADD CONSTRAINT community_actions_check_received_ban CHECK (NOT (received_ban IS NULL AND ban_expires IS NOT NULL));
|
||||
|
||||
ALTER TABLE instance_actions
|
||||
ALTER COLUMN blocked DROP NOT NULL,
|
||||
ALTER COLUMN blocked DROP DEFAULT;
|
||||
|
||||
ALTER TABLE person_actions
|
||||
ALTER COLUMN followed DROP NOT NULL,
|
||||
ALTER COLUMN followed DROP DEFAULT,
|
||||
ALTER COLUMN follow_pending DROP NOT NULL,
|
||||
ADD COLUMN blocked timestamptz,
|
||||
ADD CONSTRAINT person_actions_check_followed CHECK ((followed IS NULL) = (follow_pending IS NULL));
|
||||
|
||||
ALTER TABLE post_actions
|
||||
ALTER COLUMN read DROP NOT NULL,
|
||||
ALTER COLUMN read DROP DEFAULT,
|
||||
ADD COLUMN read_comments timestamptz,
|
||||
ADD COLUMN read_comments_amount bigint,
|
||||
ADD COLUMN saved timestamptz,
|
||||
ADD COLUMN liked timestamptz,
|
||||
ADD COLUMN like_score smallint,
|
||||
ADD COLUMN hidden timestamptz,
|
||||
ADD CONSTRAINT post_actions_check_read_comments CHECK ((read_comments IS NULL) = (read_comments_amount IS NULL)),
|
||||
ADD CONSTRAINT post_actions_check_liked CHECK ((liked IS NULL) = (like_score IS NULL));
|
||||
|
||||
-- Add actions from other old tables to the new tables
|
||||
INSERT INTO comment_actions (person_id, comment_id, saved)
|
||||
SELECT
|
||||
person_id,
|
||||
comment_id,
|
||||
published
|
||||
FROM
|
||||
comment_saved
|
||||
ON CONFLICT (person_id,
|
||||
comment_id)
|
||||
DO UPDATE SET
|
||||
saved = excluded.saved;
|
||||
|
||||
INSERT INTO community_actions (person_id, community_id, blocked)
|
||||
SELECT
|
||||
person_id,
|
||||
community_id,
|
||||
published
|
||||
FROM
|
||||
community_block
|
||||
ON CONFLICT (person_id,
|
||||
community_id)
|
||||
DO UPDATE SET
|
||||
person_id = excluded.person_id,
|
||||
community_id = excluded.community_id,
|
||||
blocked = excluded.blocked;
|
||||
|
||||
INSERT INTO community_actions (person_id, community_id, became_moderator)
|
||||
SELECT
|
||||
person_id,
|
||||
community_id,
|
||||
published
|
||||
FROM
|
||||
community_moderator
|
||||
ON CONFLICT (person_id,
|
||||
community_id)
|
||||
DO UPDATE SET
|
||||
person_id = excluded.person_id,
|
||||
community_id = excluded.community_id,
|
||||
became_moderator = excluded.became_moderator;
|
||||
|
||||
INSERT INTO community_actions (person_id, community_id, received_ban, ban_expires)
|
||||
SELECT
|
||||
person_id,
|
||||
community_id,
|
||||
published,
|
||||
expires
|
||||
FROM
|
||||
community_person_ban
|
||||
ON CONFLICT (person_id,
|
||||
community_id)
|
||||
DO UPDATE SET
|
||||
person_id = excluded.person_id,
|
||||
community_id = excluded.community_id,
|
||||
received_ban = excluded.received_ban,
|
||||
ban_expires = excluded.ban_expires;
|
||||
|
||||
INSERT INTO person_actions (person_id, target_id, blocked)
|
||||
SELECT
|
||||
person_id,
|
||||
target_id,
|
||||
published
|
||||
FROM
|
||||
person_block
|
||||
ON CONFLICT (person_id,
|
||||
target_id)
|
||||
DO UPDATE SET
|
||||
person_id = excluded.person_id,
|
||||
target_id = excluded.target_id,
|
||||
blocked = excluded.blocked;
|
||||
|
||||
INSERT INTO post_actions (person_id, post_id, read_comments, read_comments_amount)
|
||||
SELECT
|
||||
person_id,
|
||||
post_id,
|
||||
published,
|
||||
read_comments
|
||||
FROM
|
||||
person_post_aggregates
|
||||
ON CONFLICT (person_id,
|
||||
post_id)
|
||||
DO UPDATE SET
|
||||
read_comments = excluded.read_comments,
|
||||
read_comments_amount = excluded.read_comments_amount;
|
||||
|
||||
INSERT INTO post_actions (person_id, post_id, hidden)
|
||||
SELECT
|
||||
person_id,
|
||||
post_id,
|
||||
published
|
||||
FROM
|
||||
post_hide
|
||||
ON CONFLICT (person_id,
|
||||
post_id)
|
||||
DO UPDATE SET
|
||||
hidden = excluded.hidden;
|
||||
|
||||
INSERT INTO post_actions (person_id, post_id, liked, like_score)
|
||||
SELECT
|
||||
person_id,
|
||||
post_id,
|
||||
published,
|
||||
score
|
||||
FROM
|
||||
post_like
|
||||
ON CONFLICT (person_id,
|
||||
post_id)
|
||||
DO UPDATE SET
|
||||
liked = excluded.liked,
|
||||
like_score = excluded.like_score;
|
||||
|
||||
INSERT INTO post_actions (person_id, post_id, saved)
|
||||
SELECT
|
||||
person_id,
|
||||
post_id,
|
||||
published
|
||||
FROM
|
||||
post_saved
|
||||
ON CONFLICT (person_id,
|
||||
post_id)
|
||||
DO UPDATE SET
|
||||
saved = excluded.saved;
|
||||
|
||||
-- Drop old tables
|
||||
DROP TABLE comment_saved, community_block, community_moderator, community_person_ban, person_block, person_post_aggregates, post_hide, post_like, post_saved;
|
||||
|
||||
-- Rename associated stuff
|
||||
ALTER INDEX comment_like_pkey RENAME TO comment_actions_pkey;
|
||||
|
||||
ALTER INDEX idx_comment_like_comment RENAME TO idx_comment_actions_comment;
|
||||
|
||||
ALTER TABLE comment_actions RENAME CONSTRAINT comment_like_comment_id_fkey TO comment_actions_comment_id_fkey;
|
||||
|
||||
ALTER TABLE comment_actions RENAME CONSTRAINT comment_like_person_id_fkey TO comment_actions_person_id_fkey;
|
||||
|
||||
ALTER INDEX community_follower_pkey RENAME TO community_actions_pkey;
|
||||
|
||||
ALTER INDEX idx_community_follower_community RENAME TO idx_community_actions_community;
|
||||
|
||||
ALTER TABLE community_actions RENAME CONSTRAINT community_follower_community_id_fkey TO community_actions_community_id_fkey;
|
||||
|
||||
ALTER TABLE community_actions RENAME CONSTRAINT community_follower_person_id_fkey TO community_actions_person_id_fkey;
|
||||
|
||||
ALTER TABLE community_actions RENAME CONSTRAINT community_follower_approver_id_fkey TO community_actions_follow_approver_id_fkey;
|
||||
|
||||
ALTER INDEX instance_block_pkey RENAME TO instance_actions_pkey;
|
||||
|
||||
ALTER TABLE instance_actions RENAME CONSTRAINT instance_block_instance_id_fkey TO instance_actions_instance_id_fkey;
|
||||
|
||||
ALTER TABLE instance_actions RENAME CONSTRAINT instance_block_person_id_fkey TO instance_actions_person_id_fkey;
|
||||
|
||||
ALTER INDEX person_follower_pkey RENAME TO person_actions_pkey;
|
||||
|
||||
ALTER TABLE person_actions RENAME CONSTRAINT person_follower_person_id_fkey TO person_actions_target_id_fkey;
|
||||
|
||||
ALTER TABLE person_actions RENAME CONSTRAINT person_follower_follower_id_fkey TO person_actions_person_id_fkey;
|
||||
|
||||
ALTER INDEX post_read_pkey RENAME TO post_actions_pkey;
|
||||
|
||||
ALTER TABLE post_actions RENAME CONSTRAINT post_read_person_id_fkey TO post_actions_person_id_fkey;
|
||||
|
||||
ALTER TABLE post_actions RENAME CONSTRAINT post_read_post_id_fkey TO post_actions_post_id_fkey;
|
||||
|
||||
-- Rename idx_community_follower_published and add filter
|
||||
CREATE INDEX idx_community_actions_followed ON community_actions (followed)
|
||||
WHERE
|
||||
followed IS NOT NULL;
|
||||
|
||||
DROP INDEX idx_community_follower_published;
|
||||
|
||||
-- Restore indexes of dropped tables
|
||||
CREATE INDEX idx_community_actions_became_moderator ON community_actions (became_moderator)
|
||||
WHERE
|
||||
became_moderator IS NOT NULL;
|
||||
|
||||
CREATE INDEX idx_person_actions_person ON person_actions (person_id);
|
||||
|
||||
CREATE INDEX idx_person_actions_target ON person_actions (target_id);
|
||||
|
||||
CREATE INDEX idx_post_actions_person ON post_actions (person_id);
|
||||
|
||||
CREATE INDEX idx_post_actions_post ON post_actions (post_id);
|
||||
|
||||
-- Create new indexes, with `OR` being used to allow `IS NOT NULL` filters in queries to use either column in
|
||||
-- a group (e.g. `liked IS NOT NULL` and `like_score IS NOT NULL` both work)
|
||||
CREATE INDEX idx_comment_actions_liked_not_null ON comment_actions (person_id, comment_id)
|
||||
WHERE
|
||||
liked IS NOT NULL OR like_score IS NOT NULL;
|
||||
|
||||
CREATE INDEX idx_comment_actions_saved_not_null ON comment_actions (person_id, comment_id)
|
||||
WHERE
|
||||
saved IS NOT NULL;
|
||||
|
||||
CREATE INDEX idx_community_actions_followed_not_null ON community_actions (person_id, community_id)
|
||||
WHERE
|
||||
followed IS NOT NULL OR follow_state IS NOT NULL;
|
||||
|
||||
CREATE INDEX idx_community_actions_blocked_not_null ON community_actions (person_id, community_id)
|
||||
WHERE
|
||||
blocked IS NOT NULL;
|
||||
|
||||
CREATE INDEX idx_community_actions_became_moderator_not_null ON community_actions (person_id, community_id)
|
||||
WHERE
|
||||
became_moderator IS NOT NULL;
|
||||
|
||||
CREATE INDEX idx_community_actions_received_ban_not_null ON community_actions (person_id, community_id)
|
||||
WHERE
|
||||
received_ban IS NOT NULL;
|
||||
|
||||
CREATE INDEX idx_person_actions_followed_not_null ON person_actions (person_id, target_id)
|
||||
WHERE
|
||||
followed IS NOT NULL OR follow_pending IS NOT NULL;
|
||||
|
||||
CREATE INDEX idx_person_actions_blocked_not_null ON person_actions (person_id, target_id)
|
||||
WHERE
|
||||
blocked IS NOT NULL;
|
||||
|
||||
CREATE INDEX idx_post_actions_read_not_null ON post_actions (person_id, post_id)
|
||||
WHERE
|
||||
read IS NOT NULL;
|
||||
|
||||
CREATE INDEX idx_post_actions_read_comments_not_null ON post_actions (person_id, post_id)
|
||||
WHERE
|
||||
read_comments IS NOT NULL OR read_comments_amount IS NOT NULL;
|
||||
|
||||
CREATE INDEX idx_post_actions_saved_not_null ON post_actions (person_id, post_id)
|
||||
WHERE
|
||||
saved IS NOT NULL;
|
||||
|
||||
CREATE INDEX idx_post_actions_liked_not_null ON post_actions (person_id, post_id)
|
||||
WHERE
|
||||
liked IS NOT NULL OR like_score IS NOT NULL;
|
||||
|
||||
CREATE INDEX idx_post_actions_hidden_not_null ON post_actions (person_id, post_id)
|
||||
WHERE
|
||||
hidden IS NOT NULL;
|
||||
|
||||
-- This index is currently redundant because instance_actions only has 1 action type, but inconsistency
|
||||
-- with other tables would make it harder to do everything correctly when adding another action type
|
||||
CREATE INDEX idx_instance_actions_blocked_not_null ON instance_actions (person_id, instance_id)
|
||||
WHERE
|
||||
blocked IS NOT NULL;
|
||||
|
||||
-- Create new statistics for more accurate estimations of how much of an index will be read (e.g. for
|
||||
-- `(liked, like_score)`, the query planner might othewise assume that `(TRUE, FALSE)` and `(TRUE, TRUE)`
|
||||
-- are equally likely when only `(TRUE, TRUE)` is possible, which would make it severely underestimate
|
||||
-- the efficiency of using the index)
|
||||
CREATE statistics comment_actions_liked_stat ON (liked IS NULL), (like_score IS NULL)
|
||||
FROM comment_actions;
|
||||
|
||||
CREATE statistics community_actions_followed_stat ON (followed IS NULL), (follow_state IS NULL)
|
||||
FROM community_actions;
|
||||
|
||||
CREATE statistics person_actions_followed_stat ON (followed IS NULL), (follow_pending IS NULL)
|
||||
FROM person_actions;
|
||||
|
||||
CREATE statistics post_actions_read_comments_stat ON (read_comments IS NULL), (read_comments_amount IS NULL)
|
||||
FROM post_actions;
|
||||
|
||||
CREATE statistics post_actions_liked_stat ON (liked IS NULL), (like_score IS NULL), (post_id IS NULL)
|
||||
FROM post_actions;
|
||||
|
115
migrations/2024-11-12-090437_move-triggers/down.sql
Normal file
115
migrations/2024-11-12-090437_move-triggers/down.sql
Normal file
|
@ -0,0 +1,115 @@
|
|||
-- Edit community aggregates to include voters as active users
|
||||
CREATE OR REPLACE FUNCTION community_aggregates_activity (i text)
|
||||
RETURNS TABLE (
|
||||
count_ bigint,
|
||||
community_id_ integer)
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
RETURN query
|
||||
SELECT
|
||||
count(*),
|
||||
community_id
|
||||
FROM (
|
||||
SELECT
|
||||
c.creator_id,
|
||||
p.community_id
|
||||
FROM
|
||||
comment c
|
||||
INNER JOIN post p ON c.post_id = p.id
|
||||
INNER JOIN person pe ON c.creator_id = pe.id
|
||||
WHERE
|
||||
c.published > ('now'::timestamp - i::interval)
|
||||
AND pe.bot_account = FALSE
|
||||
UNION
|
||||
SELECT
|
||||
p.creator_id,
|
||||
p.community_id
|
||||
FROM
|
||||
post p
|
||||
INNER JOIN person pe ON p.creator_id = pe.id
|
||||
WHERE
|
||||
p.published > ('now'::timestamp - i::interval)
|
||||
AND pe.bot_account = FALSE
|
||||
UNION
|
||||
SELECT
|
||||
pl.person_id,
|
||||
p.community_id
|
||||
FROM
|
||||
post_like pl
|
||||
INNER JOIN post p ON pl.post_id = p.id
|
||||
INNER JOIN person pe ON pl.person_id = pe.id
|
||||
WHERE
|
||||
pl.published > ('now'::timestamp - i::interval)
|
||||
AND pe.bot_account = FALSE
|
||||
UNION
|
||||
SELECT
|
||||
cl.person_id,
|
||||
p.community_id
|
||||
FROM
|
||||
comment_like cl
|
||||
INNER JOIN comment c ON cl.comment_id = comment.id
|
||||
INNER JOIN post p ON comment.post_id = p.id
|
||||
INNER JOIN person pe ON cl.person_id = pe.id
|
||||
WHERE
|
||||
cl.published > ('now'::timestamp - i::interval)
|
||||
AND pe.bot_account = FALSE) a
|
||||
GROUP BY
|
||||
community_id;
|
||||
END;
|
||||
$$;
|
||||
|
||||
-- Edit site aggregates to include voters and people who have read posts as active users
|
||||
CREATE OR REPLACE FUNCTION site_aggregates_activity (i text)
|
||||
RETURNS integer
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
count_ integer;
|
||||
BEGIN
|
||||
SELECT
|
||||
count(*) INTO count_
|
||||
FROM (
|
||||
SELECT
|
||||
c.creator_id
|
||||
FROM
|
||||
comment c
|
||||
INNER JOIN person pe ON c.creator_id = pe.id
|
||||
WHERE
|
||||
c.published > ('now'::timestamp - i::interval)
|
||||
AND pe.local = TRUE
|
||||
AND pe.bot_account = FALSE
|
||||
UNION
|
||||
SELECT
|
||||
p.creator_id
|
||||
FROM
|
||||
post p
|
||||
INNER JOIN person pe ON p.creator_id = pe.id
|
||||
WHERE
|
||||
p.published > ('now'::timestamp - i::interval)
|
||||
AND pe.local = TRUE
|
||||
AND pe.bot_account = FALSE
|
||||
UNION
|
||||
SELECT
|
||||
pl.person_id
|
||||
FROM
|
||||
post_like pl
|
||||
INNER JOIN person pe ON pl.person_id = pe.id
|
||||
WHERE
|
||||
pl.published > ('now'::timestamp - i::interval)
|
||||
AND pe.local = TRUE
|
||||
AND pe.bot_account = FALSE
|
||||
UNION
|
||||
SELECT
|
||||
cl.person_id
|
||||
FROM
|
||||
comment_like cl
|
||||
INNER JOIN person pe ON cl.person_id = pe.id
|
||||
WHERE
|
||||
cl.published > ('now'::timestamp - i::interval)
|
||||
AND pe.local = TRUE
|
||||
AND pe.bot_account = FALSE) a;
|
||||
RETURN count_;
|
||||
END;
|
||||
$$;
|
||||
|
2
migrations/2024-11-12-090437_move-triggers/up.sql
Normal file
2
migrations/2024-11-12-090437_move-triggers/up.sql
Normal file
|
@ -0,0 +1,2 @@
|
|||
DROP FUNCTION community_aggregates_activity, site_aggregates_activity CASCADE;
|
||||
|
|
@ -22,7 +22,7 @@ use lemmy_db_schema::{
|
|||
captcha_answer,
|
||||
comment,
|
||||
community,
|
||||
community_person_ban,
|
||||
community_actions,
|
||||
instance,
|
||||
person,
|
||||
post,
|
||||
|
@ -36,7 +36,15 @@ use lemmy_db_schema::{
|
|||
post::{Post, PostUpdateForm},
|
||||
},
|
||||
traits::Crud,
|
||||
utils::{functions::coalesce, get_conn, naive_now, now, DbPool, DELETED_REPLACEMENT_TEXT},
|
||||
utils::{
|
||||
find_action,
|
||||
functions::coalesce,
|
||||
get_conn,
|
||||
naive_now,
|
||||
now,
|
||||
DbPool,
|
||||
DELETED_REPLACEMENT_TEXT,
|
||||
},
|
||||
};
|
||||
use lemmy_routes::nodeinfo::{NodeInfo, NodeInfoWellKnown};
|
||||
use lemmy_utils::error::LemmyResult;
|
||||
|
@ -395,7 +403,7 @@ async fn active_counts(pool: &mut DbPool<'_>) {
|
|||
|
||||
for (full_form, abbr) in &intervals {
|
||||
let update_site_stmt = format!(
|
||||
"update site_aggregates set users_active_{} = (select * from site_aggregates_activity('{}')) where site_id = 1",
|
||||
"update site_aggregates set users_active_{} = (select * from r.site_aggregates_activity('{}')) where site_id = 1",
|
||||
abbr, full_form
|
||||
);
|
||||
sql_query(update_site_stmt)
|
||||
|
@ -404,7 +412,7 @@ async fn active_counts(pool: &mut DbPool<'_>) {
|
|||
.inspect_err(|e| error!("Failed to update site stats: {e}"))
|
||||
.ok();
|
||||
|
||||
let update_community_stmt = format!("update community_aggregates ca set users_active_{} = mv.count_ from community_aggregates_activity('{}') mv where ca.community_id = mv.community_id_", abbr, full_form);
|
||||
let update_community_stmt = format!("update community_aggregates ca set users_active_{} = mv.count_ from r.community_aggregates_activity('{}') mv where ca.community_id = mv.community_id_", abbr, full_form);
|
||||
sql_query(update_community_stmt)
|
||||
.execute(&mut conn)
|
||||
.await
|
||||
|
@ -439,7 +447,7 @@ async fn update_banned_when_expired(pool: &mut DbPool<'_>) {
|
|||
.ok();
|
||||
|
||||
diesel::delete(
|
||||
community_person_ban::table.filter(community_person_ban::expires.lt(now().nullable())),
|
||||
community_actions::table.filter(community_actions::ban_expires.lt(now().nullable())),
|
||||
)
|
||||
.execute(&mut conn)
|
||||
.await
|
||||
|
@ -470,11 +478,10 @@ async fn publish_scheduled_posts(context: &Data<LemmyContext>) {
|
|||
.filter(not(person::banned.or(person::deleted)))
|
||||
.filter(not(community::removed.or(community::deleted)))
|
||||
// ensure that user isnt banned from community
|
||||
.filter(not(exists(
|
||||
community_person_ban::table
|
||||
.filter(community_person_ban::community_id.eq(community::id))
|
||||
.filter(community_person_ban::person_id.eq(person::id)),
|
||||
)))
|
||||
.filter(not(exists(find_action(
|
||||
community_actions::received_ban,
|
||||
(person::id, community::id),
|
||||
))))
|
||||
.select((post::all_columns, community::all_columns))
|
||||
.get_results::<(Post, Community)>(&mut conn)
|
||||
.await
|
||||
|
|
Loading…
Reference in a new issue