Beginning to add new comment_view.

This commit is contained in:
Dessalines 2020-12-15 10:28:25 -05:00
parent 79a960d8a5
commit e4714627a4
10 changed files with 962 additions and 9 deletions

View file

@ -0,0 +1,23 @@
use crate::schema::comment_aggregates;
use diesel::{result::Error, *};
use serde::Serialize;
#[derive(Queryable, Associations, Identifiable, PartialEq, Debug, Serialize, Clone)]
#[table_name = "comment_aggregates"]
pub struct CommentAggregates {
pub id: i32,
pub comment_id: i32,
pub score: i64,
pub upvotes: i64,
pub downvotes: i64,
}
impl CommentAggregates {
pub fn read(conn: &PgConnection, comment_id: i32) -> Result<Self, Error> {
comment_aggregates::table
.filter(comment_aggregates::comment_id.eq(comment_id))
.first::<Self>(conn)
}
}
// TODO add tests here

View file

@ -1,3 +1,4 @@
pub mod comment_aggregates;
pub mod community_aggregates;
pub mod post_aggregates;
pub mod site_aggregates;

View file

@ -34,6 +34,16 @@ table! {
}
}
table! {
comment_aggregates (id) {
id -> Int4,
comment_id -> Int4,
score -> Int8,
upvotes -> Int8,
downvotes -> Int8,
}
}
table! {
comment_aggregates_fast (id) {
id -> Int4,
@ -556,8 +566,61 @@ table! {
}
}
// These are necessary since diesel doesn't have self joins / aliases
table! {
comment_alias_1 (id) {
id -> Int4,
creator_id -> Int4,
post_id -> Int4,
parent_id -> Nullable<Int4>,
content -> Text,
removed -> Bool,
read -> Bool,
published -> Timestamp,
updated -> Nullable<Timestamp>,
deleted -> Bool,
ap_id -> Varchar,
local -> Bool,
}
}
table! {
user_alias_1 (id) {
id -> Int4,
name -> Varchar,
preferred_username -> Nullable<Varchar>,
password_encrypted -> Text,
email -> Nullable<Text>,
avatar -> Nullable<Text>,
admin -> Bool,
banned -> Bool,
published -> Timestamp,
updated -> Nullable<Timestamp>,
show_nsfw -> Bool,
theme -> Varchar,
default_sort_type -> Int2,
default_listing_type -> Int2,
lang -> Varchar,
show_avatars -> Bool,
send_notifications_to_email -> Bool,
matrix_user_id -> Nullable<Text>,
actor_id -> Varchar,
bio -> Nullable<Text>,
local -> Bool,
private_key -> Nullable<Text>,
public_key -> Nullable<Text>,
last_refreshed_at -> Timestamp,
banner -> Nullable<Text>,
deleted -> Bool,
}
}
joinable!(comment_alias_1 -> user_alias_1 (creator_id));
joinable!(comment -> comment_alias_1 (parent_id));
joinable!(comment -> post (post_id));
joinable!(comment -> user_ (creator_id));
joinable!(comment_aggregates -> comment (comment_id));
joinable!(comment_like -> comment (comment_id));
joinable!(comment_like -> post (post_id));
joinable!(comment_like -> user_ (user_id));
@ -606,6 +669,7 @@ allow_tables_to_appear_in_same_query!(
activity,
category,
comment,
comment_aggregates,
comment_aggregates_fast,
comment_like,
comment_report,
@ -641,4 +705,6 @@ allow_tables_to_appear_in_same_query!(
user_ban,
user_fast,
user_mention,
comment_alias_1,
user_alias_1,
);

View file

@ -1,13 +1,14 @@
use super::post::Post;
use crate::{
naive_now,
schema::{comment, comment_like, comment_saved},
schema::{comment, comment_alias_1, comment_like, comment_saved},
ApubObject,
Crud,
Likeable,
Saveable,
};
use diesel::{dsl::*, result::Error, *};
use serde::Serialize;
use url::{ParseError, Url};
// WITH RECURSIVE MyTree AS (
@ -17,7 +18,7 @@ use url::{ParseError, Url};
// )
// SELECT * FROM MyTree;
#[derive(Clone, Queryable, Associations, Identifiable, PartialEq, Debug)]
#[derive(Clone, Queryable, Associations, Identifiable, PartialEq, Debug, Serialize)]
#[belongs_to(Post)]
#[table_name = "comment"]
pub struct Comment {
@ -35,6 +36,24 @@ pub struct Comment {
pub local: bool,
}
#[derive(Clone, Queryable, Associations, Identifiable, PartialEq, Debug, Serialize)]
#[belongs_to(Post)]
#[table_name = "comment_alias_1"]
pub struct CommentAlias1 {
pub id: i32,
pub creator_id: i32,
pub post_id: i32,
pub parent_id: Option<i32>,
pub content: String,
pub removed: bool,
pub read: bool, // Whether the recipient has read the comment or not
pub published: chrono::NaiveDateTime,
pub updated: Option<chrono::NaiveDateTime>,
pub deleted: bool,
pub ap_id: String,
pub local: bool,
}
#[derive(Insertable, AsChangeset, Clone)]
#[table_name = "comment"]
pub struct CommentForm {

View file

@ -1,7 +1,7 @@
use crate::{
is_email_regex,
naive_now,
schema::{user_, user_::dsl::*},
schema::{user_, user_::dsl::*, user_alias_1},
ApubObject,
Crud,
};
@ -103,6 +103,98 @@ mod safe_type {
}
}
#[derive(Clone, Queryable, Identifiable, PartialEq, Debug, Serialize)]
#[table_name = "user_alias_1"]
pub struct UserAlias1 {
pub id: i32,
pub name: String,
pub preferred_username: Option<String>,
pub password_encrypted: String,
pub email: Option<String>,
pub avatar: Option<String>,
pub admin: bool,
pub banned: bool,
pub published: chrono::NaiveDateTime,
pub updated: Option<chrono::NaiveDateTime>,
pub show_nsfw: bool,
pub theme: String,
pub default_sort_type: i16,
pub default_listing_type: i16,
pub lang: String,
pub show_avatars: bool,
pub send_notifications_to_email: bool,
pub matrix_user_id: Option<String>,
pub actor_id: String,
pub bio: Option<String>,
pub local: bool,
pub private_key: Option<String>,
pub public_key: Option<String>,
pub last_refreshed_at: chrono::NaiveDateTime,
pub banner: Option<String>,
pub deleted: bool,
}
#[derive(Clone, Queryable, Identifiable, PartialEq, Debug, Serialize)]
#[table_name = "user_alias_1"]
pub struct UserSafeAlias1 {
pub id: i32,
pub name: String,
pub preferred_username: Option<String>,
pub avatar: Option<String>,
pub admin: bool,
pub banned: bool,
pub published: chrono::NaiveDateTime,
pub updated: Option<chrono::NaiveDateTime>,
pub matrix_user_id: Option<String>,
pub actor_id: String,
pub bio: Option<String>,
pub local: bool,
pub banner: Option<String>,
pub deleted: bool,
}
mod safe_type_alias {
use crate::{schema::user_alias_1::columns::*, source::user::UserAlias1, ToSafe};
type Columns = (
id,
name,
preferred_username,
avatar,
admin,
banned,
published,
updated,
matrix_user_id,
actor_id,
bio,
local,
banner,
deleted,
);
impl ToSafe for UserAlias1 {
type SafeColumns = Columns;
fn safe_columns_tuple() -> Self::SafeColumns {
(
id,
name,
preferred_username,
avatar,
admin,
banned,
published,
updated,
matrix_user_id,
actor_id,
bio,
local,
banner,
deleted,
)
}
}
}
#[derive(Insertable, AsChangeset, Clone)]
#[table_name = "user_"]
pub struct UserForm {

View file

@ -0,0 +1,662 @@
use crate::{
aggregates::comment_aggregates::CommentAggregates,
functions::hot_rank,
fuzzy_search,
limit_and_offset,
schema::{
comment,
comment_aggregates,
comment_alias_1,
comment_like,
comment_saved,
community,
community_follower,
community_user_ban,
post,
user_,
user_alias_1,
},
source::{
comment::{Comment, CommentAlias1, CommentSaved},
community::{Community, CommunityFollower, CommunitySafe, CommunityUserBan},
post::Post,
user::{UserAlias1, UserSafe, UserSafeAlias1, User_},
},
views::ViewToVec,
ListingType,
MaybeOptional,
SortType,
ToSafe,
};
use diesel::{result::Error, *};
use serde::Serialize;
#[derive(Debug, PartialEq, Serialize, Clone)]
pub struct CommentView {
pub comment: Comment,
pub creator: UserSafe,
pub recipient: Option<UserSafeAlias1>, // Left joins to comment and user
pub post: Post,
pub community: CommunitySafe,
pub counts: CommentAggregates,
pub creator_banned_from_community: bool, // Left Join to CommunityUserBan
pub subscribed: bool, // Left join to CommunityFollower
pub saved: bool, // Left join to CommentSaved
pub my_vote: Option<i16>, // Left join to CommentLike
}
type CommentViewTuple = (
Comment,
UserSafe,
Option<CommentAlias1>,
Option<UserSafeAlias1>,
Post,
CommunitySafe,
CommentAggregates,
Option<CommunityUserBan>,
Option<CommunityFollower>,
Option<CommentSaved>,
Option<i16>,
);
impl CommentView {
pub fn read(
conn: &PgConnection,
comment_id: i32,
my_user_id: Option<i32>,
) -> Result<Self, Error> {
// The left join below will return None in this case
let user_id_join = my_user_id.unwrap_or(-1);
let (
comment,
creator,
_parent_comment,
recipient,
post,
community,
counts,
creator_banned_from_community,
subscribed,
saved,
my_vote,
) = comment::table
.find(comment_id)
.inner_join(user_::table)
// recipient here
.left_join(comment_alias_1::table.on(comment_alias_1::id.nullable().eq(comment::parent_id)))
.left_join(user_alias_1::table.on(user_alias_1::id.eq(comment_alias_1::creator_id)))
.inner_join(post::table)
.inner_join(community::table.on(post::community_id.eq(community::id)))
.inner_join(comment_aggregates::table)
.left_join(
community_user_ban::table.on(
community::id
.eq(community_user_ban::community_id)
.and(community_user_ban::user_id.eq(comment::creator_id)),
),
)
.left_join(
community_follower::table.on(
post::community_id
.eq(community_follower::community_id)
.and(community_follower::user_id.eq(user_id_join)),
),
)
.left_join(
comment_saved::table.on(
comment::id
.eq(comment_saved::comment_id)
.and(comment_saved::user_id.eq(user_id_join)),
),
)
.left_join(
comment_like::table.on(
comment::id
.eq(comment_like::comment_id)
.and(comment_like::user_id.eq(user_id_join)),
),
)
.select((
comment::all_columns,
User_::safe_columns_tuple(),
comment_alias_1::all_columns.nullable(),
UserAlias1::safe_columns_tuple().nullable(),
post::all_columns,
Community::safe_columns_tuple(),
comment_aggregates::all_columns,
community_user_ban::all_columns.nullable(),
community_follower::all_columns.nullable(),
comment_saved::all_columns.nullable(),
comment_like::score.nullable(),
))
.first::<CommentViewTuple>(conn)?;
Ok(CommentView {
comment,
recipient,
post,
creator,
community,
counts,
creator_banned_from_community: creator_banned_from_community.is_some(),
subscribed: subscribed.is_some(),
saved: saved.is_some(),
my_vote,
})
}
}
mod join_types {
use crate::schema::{
comment,
comment_aggregates,
comment_alias_1,
comment_like,
comment_saved,
community,
community_follower,
community_user_ban,
post,
user_,
user_alias_1,
};
use diesel::{
pg::Pg,
query_builder::BoxedSelectStatement,
query_source::joins::{Inner, Join, JoinOn, LeftOuter},
sql_types::*,
};
// /// TODO awful, but necessary because of the boxed join
pub(super) type BoxedCommentJoin<'a> = BoxedSelectStatement<
'a,
(
(
Integer,
Integer,
Integer,
Nullable<Integer>,
Text,
Bool,
Bool,
Timestamp,
Nullable<Timestamp>,
Bool,
Text,
Bool,
),
(
Integer,
Text,
Nullable<Text>,
Nullable<Text>,
Bool,
Bool,
Timestamp,
Nullable<Timestamp>,
Nullable<Text>,
Text,
Nullable<Text>,
Bool,
Nullable<Text>,
Bool,
),
Nullable<(
Integer,
Integer,
Integer,
Nullable<Integer>,
Text,
Bool,
Bool,
Timestamp,
Nullable<Timestamp>,
Bool,
Text,
Bool,
)>,
Nullable<(
Integer,
Text,
Nullable<Text>,
Nullable<Text>,
Bool,
Bool,
Timestamp,
Nullable<Timestamp>,
Nullable<Text>,
Text,
Nullable<Text>,
Bool,
Nullable<Text>,
Bool,
)>,
(
Integer,
Text,
Nullable<Text>,
Nullable<Text>,
Integer,
Integer,
Bool,
Bool,
Timestamp,
Nullable<Timestamp>,
Bool,
Bool,
Bool,
Nullable<Text>,
Nullable<Text>,
Nullable<Text>,
Nullable<Text>,
Text,
Bool,
),
(
Integer,
Text,
Text,
Nullable<Text>,
Integer,
Integer,
Bool,
Timestamp,
Nullable<Timestamp>,
Bool,
Bool,
Text,
Bool,
Nullable<Text>,
Nullable<Text>,
),
(Integer, Integer, BigInt, BigInt, BigInt),
Nullable<(Integer, Integer, Integer, Timestamp)>,
Nullable<(Integer, Integer, Integer, Timestamp, Nullable<Bool>)>,
Nullable<(Integer, Integer, Integer, Timestamp)>,
Nullable<SmallInt>,
),
JoinOn<
Join<
JoinOn<
Join<
JoinOn<
Join<
JoinOn<
Join<
JoinOn<
Join<
JoinOn<
Join<
JoinOn<
Join<
JoinOn<
Join<
JoinOn<
Join<
JoinOn<
Join<comment::table, user_::table, Inner>,
diesel::expression::operators::Eq<
diesel::expression::nullable::Nullable<
comment::columns::creator_id,
>,
diesel::expression::nullable::Nullable<
user_::columns::id,
>,
>,
>,
comment_alias_1::table,
LeftOuter,
>,
diesel::expression::operators::Eq<
diesel::expression::nullable::Nullable<
comment_alias_1::columns::id,
>,
comment::columns::parent_id,
>,
>,
user_alias_1::table,
LeftOuter,
>,
diesel::expression::operators::Eq<
user_alias_1::columns::id,
comment_alias_1::columns::creator_id,
>,
>,
post::table,
Inner,
>,
diesel::expression::operators::Eq<
diesel::expression::nullable::Nullable<comment::columns::post_id>,
diesel::expression::nullable::Nullable<post::columns::id>,
>,
>,
community::table,
Inner,
>,
diesel::expression::operators::Eq<
post::columns::community_id,
community::columns::id,
>,
>,
comment_aggregates::table,
Inner,
>,
diesel::expression::operators::Eq<
diesel::expression::nullable::Nullable<
comment_aggregates::columns::comment_id,
>,
diesel::expression::nullable::Nullable<comment::columns::id>,
>,
>,
community_user_ban::table,
LeftOuter,
>,
diesel::expression::operators::And<
diesel::expression::operators::Eq<
community::columns::id,
community_user_ban::columns::community_id,
>,
diesel::expression::operators::Eq<
community_user_ban::columns::user_id,
comment::columns::creator_id,
>,
>,
>,
community_follower::table,
LeftOuter,
>,
diesel::expression::operators::And<
diesel::expression::operators::Eq<
post::columns::community_id,
community_follower::columns::community_id,
>,
diesel::expression::operators::Eq<
community_follower::columns::user_id,
diesel::expression::bound::Bound<Integer, i32>,
>,
>,
>,
comment_saved::table,
LeftOuter,
>,
diesel::expression::operators::And<
diesel::expression::operators::Eq<
comment::columns::id,
comment_saved::columns::comment_id,
>,
diesel::expression::operators::Eq<
comment_saved::columns::user_id,
diesel::expression::bound::Bound<Integer, i32>,
>,
>,
>,
comment_like::table,
LeftOuter,
>,
diesel::expression::operators::And<
diesel::expression::operators::Eq<comment::columns::id, comment_like::columns::comment_id>,
diesel::expression::operators::Eq<
comment_like::columns::user_id,
diesel::expression::bound::Bound<Integer, i32>,
>,
>,
>,
Pg,
>;
}
pub struct CommentQueryBuilder<'a> {
conn: &'a PgConnection,
query: join_types::BoxedCommentJoin<'a>,
listing_type: ListingType,
sort: &'a SortType,
for_community_id: Option<i32>,
for_community_name: Option<String>,
for_post_id: Option<i32>,
for_creator_id: Option<i32>,
for_recipient_id: Option<i32>,
search_term: Option<String>,
saved_only: bool,
unread_only: bool,
page: Option<i64>,
limit: Option<i64>,
}
impl<'a> CommentQueryBuilder<'a> {
pub fn create(conn: &'a PgConnection, my_user_id: Option<i32>) -> Self {
// The left join below will return None in this case
let user_id_join = my_user_id.unwrap_or(-1);
let query = comment::table
.inner_join(user_::table)
// recipient here
.left_join(comment_alias_1::table.on(comment_alias_1::id.nullable().eq(comment::parent_id)))
.left_join(user_alias_1::table.on(user_alias_1::id.eq(comment_alias_1::creator_id)))
.inner_join(post::table)
.inner_join(community::table.on(post::community_id.eq(community::id)))
.inner_join(comment_aggregates::table)
.left_join(
community_user_ban::table.on(
community::id
.eq(community_user_ban::community_id)
.and(community_user_ban::user_id.eq(comment::creator_id)),
),
)
.left_join(
community_follower::table.on(
post::community_id
.eq(community_follower::community_id)
.and(community_follower::user_id.eq(user_id_join)),
),
)
.left_join(
comment_saved::table.on(
comment::id
.eq(comment_saved::comment_id)
.and(comment_saved::user_id.eq(user_id_join)),
),
)
.left_join(
comment_like::table.on(
comment::id
.eq(comment_like::comment_id)
.and(comment_like::user_id.eq(user_id_join)),
),
)
.select((
comment::all_columns,
User_::safe_columns_tuple(),
comment_alias_1::all_columns.nullable(),
UserAlias1::safe_columns_tuple().nullable(),
post::all_columns,
Community::safe_columns_tuple(),
comment_aggregates::all_columns,
community_user_ban::all_columns.nullable(),
community_follower::all_columns.nullable(),
comment_saved::all_columns.nullable(),
comment_like::score.nullable(),
))
.into_boxed();
CommentQueryBuilder {
conn,
query,
listing_type: ListingType::All,
sort: &SortType::New,
for_community_id: None,
for_community_name: None,
for_post_id: None,
for_creator_id: None,
for_recipient_id: None,
search_term: None,
saved_only: false,
unread_only: false,
page: None,
limit: None,
}
}
pub fn listing_type(mut self, listing_type: ListingType) -> Self {
self.listing_type = listing_type;
self
}
pub fn sort(mut self, sort: &'a SortType) -> Self {
self.sort = sort;
self
}
pub fn for_post_id<T: MaybeOptional<i32>>(mut self, for_post_id: T) -> Self {
self.for_post_id = for_post_id.get_optional();
self
}
pub fn for_creator_id<T: MaybeOptional<i32>>(mut self, for_creator_id: T) -> Self {
self.for_creator_id = for_creator_id.get_optional();
self
}
pub fn for_recipient_id<T: MaybeOptional<i32>>(mut self, for_recipient_id: T) -> Self {
self.for_creator_id = for_recipient_id.get_optional();
self
}
pub fn for_community_id<T: MaybeOptional<i32>>(mut self, for_community_id: T) -> Self {
self.for_community_id = for_community_id.get_optional();
self
}
pub fn for_community_name<T: MaybeOptional<String>>(mut self, for_community_name: T) -> Self {
self.for_community_name = for_community_name.get_optional();
self
}
pub fn search_term<T: MaybeOptional<String>>(mut self, search_term: T) -> Self {
self.search_term = search_term.get_optional();
self
}
pub fn saved_only(mut self, saved_only: bool) -> Self {
self.saved_only = saved_only;
self
}
pub fn unread_only(mut self, unread_only: bool) -> Self {
self.unread_only = unread_only;
self
}
pub fn page<T: MaybeOptional<i64>>(mut self, page: T) -> Self {
self.page = page.get_optional();
self
}
pub fn limit<T: MaybeOptional<i64>>(mut self, limit: T) -> Self {
self.limit = limit.get_optional();
self
}
pub fn list(self) -> Result<Vec<CommentView>, Error> {
use diesel::dsl::*;
let mut query = self.query;
// The replies
if let Some(for_recipient_id) = self.for_recipient_id {
query = query
// TODO needs lots of testing
.filter(user_alias_1::id.eq(for_recipient_id))
.filter(comment::deleted.eq(false))
.filter(comment::removed.eq(false));
}
if self.unread_only {
query = query.filter(comment::read.eq(false));
}
if let Some(for_creator_id) = self.for_creator_id {
query = query.filter(comment::creator_id.eq(for_creator_id));
};
if let Some(for_community_id) = self.for_community_id {
query = query.filter(post::community_id.eq(for_community_id));
}
if let Some(for_community_name) = self.for_community_name {
query = query
.filter(community::name.eq(for_community_name))
.filter(comment::local.eq(true));
}
if let Some(for_post_id) = self.for_post_id {
query = query.filter(comment::post_id.eq(for_post_id));
};
if let Some(search_term) = self.search_term {
query = query.filter(comment::content.ilike(fuzzy_search(&search_term)));
};
query = match self.listing_type {
// ListingType::Subscribed => query.filter(community_follower::subscribed.eq(true)),
ListingType::Subscribed => query.filter(community_follower::user_id.is_not_null()), // TODO could be this: and(community_follower::user_id.eq(user_id_join)),
ListingType::Local => query.filter(community::local.eq(true)),
_ => query,
};
if self.saved_only {
query = query.filter(comment_saved::id.is_not_null());
}
query = match self.sort {
SortType::Hot | SortType::Active => query
.order_by(hot_rank(comment_aggregates::score, comment::published).desc())
.then_order_by(comment::published.desc()),
SortType::New => query.order_by(comment::published.desc()),
SortType::TopAll => query.order_by(comment_aggregates::score.desc()),
SortType::TopYear => query
.filter(comment::published.gt(now - 1.years()))
.order_by(comment_aggregates::score.desc()),
SortType::TopMonth => query
.filter(comment::published.gt(now - 1.months()))
.order_by(comment_aggregates::score.desc()),
SortType::TopWeek => query
.filter(comment::published.gt(now - 1.weeks()))
.order_by(comment_aggregates::score.desc()),
SortType::TopDay => query
.filter(comment::published.gt(now - 1.days()))
.order_by(comment_aggregates::score.desc()),
};
let (limit, offset) = limit_and_offset(self.page, self.limit);
// Note: deleted and removed comments are done on the front side
let res = query
.limit(limit)
.offset(offset)
.load::<CommentViewTuple>(self.conn)?;
Ok(CommentView::to_vec(res))
}
}
impl ViewToVec for CommentView {
type DbTuple = CommentViewTuple;
fn to_vec(posts: Vec<Self::DbTuple>) -> Vec<Self> {
posts
.iter()
.map(|a| Self {
comment: a.0.to_owned(),
creator: a.1.to_owned(),
recipient: a.3.to_owned(),
post: a.4.to_owned(),
community: a.5.to_owned(),
counts: a.6.to_owned(),
creator_banned_from_community: a.7.is_some(),
subscribed: a.8.is_some(),
saved: a.9.is_some(),
my_vote: a.10,
})
.collect::<Vec<Self>>()
}
}

View file

@ -1,3 +1,4 @@
pub mod comment_view;
pub mod community_follower_view;
pub mod community_moderator_view;
pub mod community_user_ban_view;

View file

@ -33,12 +33,12 @@ pub struct PostView {
pub post: Post,
pub creator: UserSafe,
pub community: CommunitySafe,
pub counts: PostAggregates,
pub subscribed: bool, // Left join to CommunityFollower
pub creator_banned_from_community: bool, // Left Join to CommunityUserBan
pub saved: bool, // Left join to PostSaved
pub read: bool, // Left join to PostRead
pub my_vote: Option<i16>, // Left join to PostLike
pub counts: PostAggregates,
pub subscribed: bool, // Left join to CommunityFollower
pub saved: bool, // Left join to PostSaved
pub read: bool, // Left join to PostRead
pub my_vote: Option<i16>, // Left join to PostLike
}
type PostViewTuple = (
@ -76,7 +76,7 @@ impl PostView {
community_user_ban::table.on(
post::community_id
.eq(community_user_ban::community_id)
.and(community_user_ban::user_id.eq(community::creator_id)),
.and(community_user_ban::user_id.eq(post::creator_id)),
),
)
.inner_join(post_aggregates::table)

View file

@ -0,0 +1,7 @@
-- comment aggregates
drop table comment_aggregates;
drop trigger comment_aggregates_comment on comment;
drop trigger comment_aggregates_score on comment_like;
drop function
comment_aggregates_comment,
comment_aggregates_score;

View file

@ -0,0 +1,82 @@
-- Add comment aggregates
create table comment_aggregates (
id serial primary key,
comment_id int references comment on update cascade on delete cascade not null,
score bigint not null default 0,
upvotes bigint not null default 0,
downvotes bigint not null default 0,
unique (comment_id)
);
insert into comment_aggregates (comment_id, score, upvotes, downvotes)
select
c.id,
COALESCE(cl.total, 0::bigint) AS score,
COALESCE(cl.up, 0::bigint) AS upvotes,
COALESCE(cl.down, 0::bigint) AS downvotes
from comment c
left join ( select l.comment_id as id,
sum(l.score) as total,
count(
case
when l.score = 1 then 1
else null::integer
end) as up,
count(
case
when l.score = '-1'::integer then 1
else null::integer
end) as down
from comment_like l
group by l.comment_id) cl on cl.id = c.id;
-- Add comment aggregate triggers
-- initial comment add
create function comment_aggregates_comment()
returns trigger language plpgsql
as $$
begin
IF (TG_OP = 'INSERT') THEN
insert into comment_aggregates (comment_id) values (NEW.id);
ELSIF (TG_OP = 'DELETE') THEN
delete from comment_aggregates where comment_id = OLD.id;
END IF;
return null;
end $$;
create trigger comment_aggregates_comment
after insert or delete on comment
for each row
execute procedure comment_aggregates_comment();
-- comment score
create function comment_aggregates_score()
returns trigger language plpgsql
as $$
begin
IF (TG_OP = 'INSERT') THEN
update comment_aggregates ca
set score = score + NEW.score,
upvotes = case when NEW.score = 1 then upvotes + 1 else upvotes end,
downvotes = case when NEW.score = -1 then downvotes + 1 else downvotes end
where ca.comment_id = NEW.comment_id;
ELSIF (TG_OP = 'DELETE') THEN
-- Join to comment because that comment may not exist anymore
update comment_aggregates ca
set score = score - OLD.score,
upvotes = case when OLD.score = 1 then upvotes - 1 else upvotes end,
downvotes = case when OLD.score = -1 then downvotes - 1 else downvotes end
from comment c
where ca.comment_id = c.id
and ca.comment_id = OLD.comment_id;
END IF;
return null;
end $$;
create trigger comment_aggregates_score
after insert or delete on comment_like
for each row
execute procedure comment_aggregates_score();