2 aggregates::post_aggregates::PostAggregates,
3 community::{Community, CommunityFollower, CommunitySafe, CommunityUserBan},
7 post::{Post, PostRead, PostSaved},
19 user::{UserSafe, User_},
25 use diesel::{result::Error, *};
28 #[derive(Debug, Serialize, Clone)]
31 pub creator: UserSafe,
32 pub community: CommunitySafe,
33 pub counts: PostAggregates,
34 pub subscribed: bool, // Left join to CommunityFollower
35 pub banned_from_community: bool, // Left Join to CommunityUserBan
36 pub saved: bool, // Left join to PostSaved
37 pub read: bool, // Left join to PostRead
38 pub my_vote: Option<i16>, // Left join to PostLike
46 Option<CommunityFollower>,
47 Option<CommunityUserBan>,
54 pub fn read(conn: &PgConnection, post_id: i32, my_user_id: Option<i32>) -> Result<Self, Error> {
55 // The left join below will return None in this case
56 let user_id_join = my_user_id.unwrap_or(-1);
58 let (post, creator, community, counts, follower, banned_from_community, saved, read, my_vote) =
61 .inner_join(user_::table)
62 .inner_join(community::table)
63 .inner_join(post_aggregates::table)
65 community_follower::table.on(
67 .eq(community_follower::community_id)
68 .and(community_follower::user_id.eq(user_id_join)),
72 community_user_ban::table.on(
74 .eq(community_user_ban::community_id)
75 .and(community_user_ban::user_id.eq(user_id_join)),
81 .eq(post_saved::post_id)
82 .and(post_saved::user_id.eq(user_id_join)),
88 .eq(post_read::post_id)
89 .and(post_read::user_id.eq(user_id_join)),
95 .eq(post_like::post_id)
96 .and(post_like::user_id.eq(user_id_join)),
101 User_::safe_columns_tuple(),
102 Community::safe_columns_tuple(),
103 post_aggregates::all_columns,
104 community_follower::all_columns.nullable(),
105 community_user_ban::all_columns.nullable(),
106 post_saved::all_columns.nullable(),
107 post_read::all_columns.nullable(),
108 post_like::score.nullable(),
110 .first::<OutputTuple>(conn)?;
117 subscribed: follower.is_some(),
118 banned_from_community: banned_from_community.is_some(),
119 saved: saved.is_some(),
120 read: read.is_some(),
140 query_builder::BoxedSelectStatement,
141 query_source::joins::{Inner, Join, JoinOn, LeftOuter},
145 /// TODO awful, but necessary because of the boxed join
146 pub(super) type BoxedPostJoin<'a> = BoxedSelectStatement<
203 (Integer, Integer, BigInt, BigInt, BigInt, BigInt, Timestamp),
204 Nullable<(Integer, Integer, Integer, Timestamp, Nullable<Bool>)>,
205 Nullable<(Integer, Integer, Integer, Timestamp)>,
206 Nullable<(Integer, Integer, Integer, Timestamp)>,
207 Nullable<(Integer, Integer, Integer, Timestamp)>,
225 Join<post::table, user_::table, Inner>,
226 diesel::expression::operators::Eq<
227 diesel::expression::nullable::Nullable<
228 post::columns::creator_id,
230 diesel::expression::nullable::Nullable<user_::columns::id>,
236 diesel::expression::operators::Eq<
237 diesel::expression::nullable::Nullable<post::columns::community_id>,
238 diesel::expression::nullable::Nullable<community::columns::id>,
241 post_aggregates::table,
244 diesel::expression::operators::Eq<
245 diesel::expression::nullable::Nullable<
246 post_aggregates::columns::post_id,
248 diesel::expression::nullable::Nullable<post::columns::id>,
251 community_follower::table,
254 diesel::expression::operators::And<
255 diesel::expression::operators::Eq<
256 post::columns::community_id,
257 community_follower::columns::community_id,
259 diesel::expression::operators::Eq<
260 community_follower::columns::user_id,
261 diesel::expression::bound::Bound<diesel::sql_types::Integer, i32>,
265 community_user_ban::table,
268 diesel::expression::operators::And<
269 diesel::expression::operators::Eq<
270 post::columns::community_id,
271 community_user_ban::columns::community_id,
273 diesel::expression::operators::Eq<
274 community_user_ban::columns::user_id,
275 diesel::expression::bound::Bound<diesel::sql_types::Integer, i32>,
282 diesel::expression::operators::And<
283 diesel::expression::operators::Eq<post::columns::id, post_saved::columns::post_id>,
284 diesel::expression::operators::Eq<
285 post_saved::columns::user_id,
286 diesel::expression::bound::Bound<diesel::sql_types::Integer, i32>,
293 diesel::expression::operators::And<
294 diesel::expression::operators::Eq<post::columns::id, post_read::columns::post_id>,
295 diesel::expression::operators::Eq<
296 post_read::columns::user_id,
297 diesel::expression::bound::Bound<diesel::sql_types::Integer, i32>,
304 diesel::expression::operators::And<
305 diesel::expression::operators::Eq<post::columns::id, post_like::columns::post_id>,
306 diesel::expression::operators::Eq<
307 post_like::columns::user_id,
308 diesel::expression::bound::Bound<diesel::sql_types::Integer, i32>,
316 pub struct PostQueryBuilder<'a> {
317 conn: &'a PgConnection,
318 query: join_types::BoxedPostJoin<'a>,
319 listing_type: &'a ListingType,
321 for_creator_id: Option<i32>,
322 for_community_id: Option<i32>,
323 for_community_name: Option<String>,
324 search_term: Option<String>,
325 url_search: Option<String>,
333 impl<'a> PostQueryBuilder<'a> {
334 pub fn create(conn: &'a PgConnection, my_user_id: Option<i32>) -> Self {
335 // The left join below will return None in this case
336 let user_id_join = my_user_id.unwrap_or(-1);
338 let query = post::table
339 .inner_join(user_::table)
340 .inner_join(community::table)
341 .inner_join(post_aggregates::table)
343 community_follower::table.on(
345 .eq(community_follower::community_id)
346 .and(community_follower::user_id.eq(user_id_join)),
350 community_user_ban::table.on(
352 .eq(community_user_ban::community_id)
353 .and(community_user_ban::user_id.eq(user_id_join)),
357 post_saved::table.on(
359 .eq(post_saved::post_id)
360 .and(post_saved::user_id.eq(user_id_join)),
366 .eq(post_read::post_id)
367 .and(post_read::user_id.eq(user_id_join)),
373 .eq(post_like::post_id)
374 .and(post_like::user_id.eq(user_id_join)),
379 User_::safe_columns_tuple(),
380 Community::safe_columns_tuple(),
381 post_aggregates::all_columns,
382 community_follower::all_columns.nullable(),
383 community_user_ban::all_columns.nullable(),
384 post_saved::all_columns.nullable(),
385 post_read::all_columns.nullable(),
386 post_like::score.nullable(),
393 listing_type: &ListingType::All,
394 sort: &SortType::Hot,
395 for_creator_id: None,
396 for_community_id: None,
397 for_community_name: None,
408 pub fn listing_type(mut self, listing_type: &'a ListingType) -> Self {
409 self.listing_type = listing_type;
413 pub fn sort(mut self, sort: &'a SortType) -> Self {
418 pub fn for_community_id<T: MaybeOptional<i32>>(mut self, for_community_id: T) -> Self {
419 self.for_community_id = for_community_id.get_optional();
423 pub fn for_community_name<T: MaybeOptional<String>>(mut self, for_community_name: T) -> Self {
424 self.for_community_name = for_community_name.get_optional();
428 pub fn for_creator_id<T: MaybeOptional<i32>>(mut self, for_creator_id: T) -> Self {
429 self.for_creator_id = for_creator_id.get_optional();
433 pub fn search_term<T: MaybeOptional<String>>(mut self, search_term: T) -> Self {
434 self.search_term = search_term.get_optional();
438 pub fn url_search<T: MaybeOptional<String>>(mut self, url_search: T) -> Self {
439 self.url_search = url_search.get_optional();
443 pub fn show_nsfw(mut self, show_nsfw: bool) -> Self {
444 self.show_nsfw = show_nsfw;
448 pub fn saved_only(mut self, saved_only: bool) -> Self {
449 self.saved_only = saved_only;
453 pub fn page<T: MaybeOptional<i64>>(mut self, page: T) -> Self {
454 self.page = page.get_optional();
458 pub fn limit<T: MaybeOptional<i64>>(mut self, limit: T) -> Self {
459 self.limit = limit.get_optional();
463 pub fn list(self) -> Result<Vec<PostView>, Error> {
466 let mut query = self.query;
468 query = match self.listing_type {
469 ListingType::Subscribed => query.filter(community_follower::user_id.is_not_null()), // TODO could be this: and(community_follower::user_id.eq(user_id_join)),
470 ListingType::Local => query.filter(community::local.eq(true)),
474 if let Some(for_community_id) = self.for_community_id {
476 .filter(post::community_id.eq(for_community_id))
477 .then_order_by(post::stickied.desc());
480 if let Some(for_community_name) = self.for_community_name {
482 .filter(community::name.eq(for_community_name))
483 .filter(community::local.eq(true))
484 .then_order_by(post::stickied.desc());
487 if let Some(url_search) = self.url_search {
488 query = query.filter(post::url.eq(url_search));
491 if let Some(search_term) = self.search_term {
492 let searcher = fuzzy_search(&search_term);
493 query = query.filter(
495 .ilike(searcher.to_owned())
496 .or(post::body.ilike(searcher)),
500 query = match self.sort {
501 SortType::Active => query
503 hot_rank(post_aggregates::score, post_aggregates::newest_comment_time).desc(),
505 .then_order_by(post::published.desc()),
506 SortType::Hot => query
507 .then_order_by(hot_rank(post_aggregates::score, post::published).desc())
508 .then_order_by(post::published.desc()),
509 SortType::New => query.then_order_by(post::published.desc()),
510 SortType::TopAll => query.then_order_by(post_aggregates::score.desc()),
511 SortType::TopYear => query
512 .filter(post::published.gt(now - 1.years()))
513 .then_order_by(post_aggregates::score.desc()),
514 SortType::TopMonth => query
515 .filter(post::published.gt(now - 1.months()))
516 .then_order_by(post_aggregates::score.desc()),
517 SortType::TopWeek => query
518 .filter(post::published.gt(now - 1.weeks()))
519 .then_order_by(post_aggregates::score.desc()),
520 SortType::TopDay => query
521 .filter(post::published.gt(now - 1.days()))
522 .then_order_by(post_aggregates::score.desc()),
525 // If its for a specific user, show the removed / deleted
526 if let Some(for_creator_id) = self.for_creator_id {
527 query = query.filter(post::creator_id.eq(for_creator_id));
532 .filter(post::nsfw.eq(false))
533 .filter(community::nsfw.eq(false));
536 // TODO These two might be wrong
538 query = query.filter(post_saved::id.is_not_null());
541 if self.unread_only {
542 query = query.filter(post_read::id.is_not_null());
545 let (limit, offset) = limit_and_offset(self.page, self.limit);
550 .filter(post::removed.eq(false))
551 .filter(post::deleted.eq(false))
552 .filter(community::removed.eq(false))
553 .filter(community::deleted.eq(false))
554 .load::<OutputTuple>(self.conn)?;
560 // TODO turn this into a trait with an associated type
561 fn to_vec(posts: Vec<OutputTuple>) -> Vec<PostView> {
565 post: a.0.to_owned(),
566 creator: a.1.to_owned(),
567 community: a.2.to_owned(),
568 counts: a.3.to_owned(),
569 subscribed: a.4.is_some(),
570 banned_from_community: a.5.is_some(),
571 saved: a.6.is_some(),
575 .collect::<Vec<PostView>>()