2 aggregates::post_aggregates::PostAggregates,
12 use diesel::{result::Error, *};
13 use lemmy_db_schema::{
26 community::{Community, CommunityFollower, CommunitySafe, CommunityUserBan},
27 post::{Post, PostRead, PostSaved},
28 user::{UserSafe, User_},
33 #[derive(Debug, PartialEq, Serialize, Clone)]
36 pub creator: UserSafe,
37 pub community: CommunitySafe,
38 pub creator_banned_from_community: bool, // Left Join to CommunityUserBan
39 pub counts: PostAggregates,
40 pub subscribed: bool, // Left join to CommunityFollower
41 pub saved: bool, // Left join to PostSaved
42 pub read: bool, // Left join to PostRead
43 pub my_vote: Option<i16>, // Left join to PostLike
46 type PostViewTuple = (
50 Option<CommunityUserBan>,
52 Option<CommunityFollower>,
59 pub fn read(conn: &PgConnection, post_id: i32, my_user_id: Option<i32>) -> Result<Self, Error> {
60 // The left join below will return None in this case
61 let user_id_join = my_user_id.unwrap_or(-1);
67 creator_banned_from_community,
75 .inner_join(user_::table)
76 .inner_join(community::table)
78 community_user_ban::table.on(
80 .eq(community_user_ban::community_id)
81 .and(community_user_ban::user_id.eq(post::creator_id)),
84 .inner_join(post_aggregates::table)
86 community_follower::table.on(
88 .eq(community_follower::community_id)
89 .and(community_follower::user_id.eq(user_id_join)),
95 .eq(post_saved::post_id)
96 .and(post_saved::user_id.eq(user_id_join)),
102 .eq(post_read::post_id)
103 .and(post_read::user_id.eq(user_id_join)),
109 .eq(post_like::post_id)
110 .and(post_like::user_id.eq(user_id_join)),
115 User_::safe_columns_tuple(),
116 Community::safe_columns_tuple(),
117 community_user_ban::all_columns.nullable(),
118 post_aggregates::all_columns,
119 community_follower::all_columns.nullable(),
120 post_saved::all_columns.nullable(),
121 post_read::all_columns.nullable(),
122 post_like::score.nullable(),
124 .first::<PostViewTuple>(conn)?;
130 creator_banned_from_community: creator_banned_from_community.is_some(),
132 subscribed: follower.is_some(),
133 saved: saved.is_some(),
134 read: read.is_some(),
140 pub struct PostQueryBuilder<'a> {
141 conn: &'a PgConnection,
142 listing_type: &'a ListingType,
144 creator_id: Option<i32>,
145 community_id: Option<i32>,
146 community_name: Option<String>,
147 my_user_id: Option<i32>,
148 search_term: Option<String>,
149 url_search: Option<String>,
157 impl<'a> PostQueryBuilder<'a> {
158 pub fn create(conn: &'a PgConnection) -> Self {
161 listing_type: &ListingType::All,
162 sort: &SortType::Hot,
165 community_name: None,
177 pub fn listing_type(mut self, listing_type: &'a ListingType) -> Self {
178 self.listing_type = listing_type;
182 pub fn sort(mut self, sort: &'a SortType) -> Self {
187 pub fn community_id<T: MaybeOptional<i32>>(mut self, community_id: T) -> Self {
188 self.community_id = community_id.get_optional();
192 pub fn my_user_id<T: MaybeOptional<i32>>(mut self, my_user_id: T) -> Self {
193 self.my_user_id = my_user_id.get_optional();
197 pub fn community_name<T: MaybeOptional<String>>(mut self, community_name: T) -> Self {
198 self.community_name = community_name.get_optional();
202 pub fn creator_id<T: MaybeOptional<i32>>(mut self, creator_id: T) -> Self {
203 self.creator_id = creator_id.get_optional();
207 pub fn search_term<T: MaybeOptional<String>>(mut self, search_term: T) -> Self {
208 self.search_term = search_term.get_optional();
212 pub fn url_search<T: MaybeOptional<String>>(mut self, url_search: T) -> Self {
213 self.url_search = url_search.get_optional();
217 pub fn show_nsfw(mut self, show_nsfw: bool) -> Self {
218 self.show_nsfw = show_nsfw;
222 pub fn saved_only(mut self, saved_only: bool) -> Self {
223 self.saved_only = saved_only;
227 pub fn page<T: MaybeOptional<i64>>(mut self, page: T) -> Self {
228 self.page = page.get_optional();
232 pub fn limit<T: MaybeOptional<i64>>(mut self, limit: T) -> Self {
233 self.limit = limit.get_optional();
237 pub fn list(self) -> Result<Vec<PostView>, Error> {
240 // The left join below will return None in this case
241 let user_id_join = self.my_user_id.unwrap_or(-1);
243 let mut query = post::table
244 .inner_join(user_::table)
245 .inner_join(community::table)
247 community_user_ban::table.on(
249 .eq(community_user_ban::community_id)
250 .and(community_user_ban::user_id.eq(community::creator_id)),
253 .inner_join(post_aggregates::table)
255 community_follower::table.on(
257 .eq(community_follower::community_id)
258 .and(community_follower::user_id.eq(user_id_join)),
262 post_saved::table.on(
264 .eq(post_saved::post_id)
265 .and(post_saved::user_id.eq(user_id_join)),
271 .eq(post_read::post_id)
272 .and(post_read::user_id.eq(user_id_join)),
278 .eq(post_like::post_id)
279 .and(post_like::user_id.eq(user_id_join)),
284 User_::safe_columns_tuple(),
285 Community::safe_columns_tuple(),
286 community_user_ban::all_columns.nullable(),
287 post_aggregates::all_columns,
288 community_follower::all_columns.nullable(),
289 post_saved::all_columns.nullable(),
290 post_read::all_columns.nullable(),
291 post_like::score.nullable(),
295 query = match self.listing_type {
296 ListingType::Subscribed => query.filter(community_follower::user_id.is_not_null()), // TODO could be this: and(community_follower::user_id.eq(user_id_join)),
297 ListingType::Local => query.filter(community::local.eq(true)),
301 if let Some(community_id) = self.community_id {
303 .filter(post::community_id.eq(community_id))
304 .then_order_by(post::stickied.desc());
307 if let Some(community_name) = self.community_name {
309 .filter(community::name.eq(community_name))
310 .filter(community::local.eq(true))
311 .then_order_by(post::stickied.desc());
314 if let Some(url_search) = self.url_search {
315 query = query.filter(post::url.eq(url_search));
318 if let Some(search_term) = self.search_term {
319 let searcher = fuzzy_search(&search_term);
320 query = query.filter(
322 .ilike(searcher.to_owned())
323 .or(post::body.ilike(searcher)),
327 // If its for a specific user, show the removed / deleted
328 if let Some(creator_id) = self.creator_id {
329 query = query.filter(post::creator_id.eq(creator_id));
334 .filter(post::nsfw.eq(false))
335 .filter(community::nsfw.eq(false));
338 // TODO These two might be wrong
340 query = query.filter(post_saved::id.is_not_null());
343 if self.unread_only {
344 query = query.filter(post_read::id.is_not_null());
347 query = match self.sort {
348 SortType::Active => query
350 hot_rank(post_aggregates::score, post_aggregates::newest_comment_time).desc(),
352 .then_order_by(post::published.desc()),
353 SortType::Hot => query
354 .then_order_by(hot_rank(post_aggregates::score, post::published).desc())
355 .then_order_by(post::published.desc()),
356 SortType::New => query.then_order_by(post::published.desc()),
357 SortType::TopAll => query.then_order_by(post_aggregates::score.desc()),
358 SortType::TopYear => query
359 .filter(post::published.gt(now - 1.years()))
360 .then_order_by(post_aggregates::score.desc()),
361 SortType::TopMonth => query
362 .filter(post::published.gt(now - 1.months()))
363 .then_order_by(post_aggregates::score.desc()),
364 SortType::TopWeek => query
365 .filter(post::published.gt(now - 1.weeks()))
366 .then_order_by(post_aggregates::score.desc()),
367 SortType::TopDay => query
368 .filter(post::published.gt(now - 1.days()))
369 .then_order_by(post_aggregates::score.desc()),
372 let (limit, offset) = limit_and_offset(self.page, self.limit);
377 .filter(post::removed.eq(false))
378 .filter(post::deleted.eq(false))
379 .filter(community::removed.eq(false))
380 .filter(community::deleted.eq(false))
381 .load::<PostViewTuple>(self.conn)?;
383 Ok(PostView::to_vec(res))
387 impl ViewToVec for PostView {
388 type DbTuple = PostViewTuple;
389 fn to_vec(posts: Vec<Self::DbTuple>) -> Vec<Self> {
393 post: a.0.to_owned(),
394 creator: a.1.to_owned(),
395 community: a.2.to_owned(),
396 creator_banned_from_community: a.3.is_some(),
397 counts: a.4.to_owned(),
398 subscribed: a.5.is_some(),
399 saved: a.6.is_some(),
403 .collect::<Vec<Self>>()
410 aggregates::post_aggregates::PostAggregates,
411 tests::establish_unpooled_connection,
412 views::post_view::{PostQueryBuilder, PostView},
417 use lemmy_db_schema::source::{community::*, post::*, user::*};
421 let conn = establish_unpooled_connection();
423 let user_name = "tegan".to_string();
424 let community_name = "test_community_3".to_string();
425 let post_name = "test post 3".to_string();
427 let new_user = UserForm {
428 name: user_name.to_owned(),
429 preferred_username: None,
430 password_encrypted: "nope".into(),
432 matrix_user_id: None,
440 theme: "browser".into(),
441 default_sort_type: SortType::Hot as i16,
442 default_listing_type: ListingType::Subscribed as i16,
443 lang: "browser".into(),
445 send_notifications_to_email: false,
451 last_refreshed_at: None,
454 let inserted_user = User_::create(&conn, &new_user).unwrap();
456 let new_community = CommunityForm {
457 name: community_name.to_owned(),
458 title: "nada".to_owned(),
460 creator_id: inserted_user.id,
470 last_refreshed_at: None,
476 let inserted_community = Community::create(&conn, &new_community).unwrap();
478 let new_post = PostForm {
479 name: post_name.to_owned(),
482 creator_id: inserted_user.id,
483 community_id: inserted_community.id,
491 embed_description: None,
499 let inserted_post = Post::create(&conn, &new_post).unwrap();
501 let post_like_form = PostLikeForm {
502 post_id: inserted_post.id,
503 user_id: inserted_user.id,
507 let inserted_post_like = PostLike::like(&conn, &post_like_form).unwrap();
509 let expected_post_like = PostLike {
510 id: inserted_post_like.id,
511 post_id: inserted_post.id,
512 user_id: inserted_user.id,
513 published: inserted_post_like.published,
517 let read_post_listings_with_user = PostQueryBuilder::create(&conn)
518 .listing_type(&ListingType::Community)
519 .sort(&SortType::New)
520 .community_id(inserted_community.id)
521 .my_user_id(inserted_user.id)
525 let read_post_listings_no_user = PostQueryBuilder::create(&conn)
526 .listing_type(&ListingType::Community)
527 .sort(&SortType::New)
528 .community_id(inserted_community.id)
532 let read_post_listing_no_user = PostView::read(&conn, inserted_post.id, None).unwrap();
533 let read_post_listing_with_user =
534 PostView::read(&conn, inserted_post.id, Some(inserted_user.id)).unwrap();
536 let agg = PostAggregates::read(&conn, inserted_post.id).unwrap();
538 // the non user version
539 let expected_post_listing_no_user = PostView {
541 id: inserted_post.id,
542 name: post_name.to_owned(),
543 creator_id: inserted_user.id,
546 published: inserted_post.published,
548 community_id: inserted_community.id,
555 embed_description: None,
558 ap_id: inserted_post.ap_id.to_owned(),
563 id: inserted_user.id,
564 name: user_name.to_owned(),
565 preferred_username: None,
566 published: inserted_user.published,
568 actor_id: inserted_user.actor_id.to_owned(),
576 matrix_user_id: None,
578 creator_banned_from_community: false,
579 community: CommunitySafe {
580 id: inserted_community.id,
581 name: community_name.to_owned(),
586 actor_id: inserted_community.actor_id.to_owned(),
588 title: "nada".to_owned(),
590 creator_id: inserted_user.id,
594 published: inserted_community.published,
596 counts: PostAggregates {
598 post_id: inserted_post.id,
603 newest_comment_time: inserted_post.published,
610 // TODO More needs to be added here
611 let mut expected_post_listing_with_user = expected_post_listing_no_user.to_owned();
612 expected_post_listing_with_user.my_vote = Some(1);
614 let like_removed = PostLike::remove(&conn, inserted_user.id, inserted_post.id).unwrap();
615 let num_deleted = Post::delete(&conn, inserted_post.id).unwrap();
616 Community::delete(&conn, inserted_community.id).unwrap();
617 User_::delete(&conn, inserted_user.id).unwrap();
621 expected_post_listing_with_user,
622 read_post_listings_with_user[0]
624 assert_eq!(expected_post_listing_with_user, read_post_listing_with_user);
625 assert_eq!(1, read_post_listings_with_user.len());
628 assert_eq!(expected_post_listing_no_user, read_post_listings_no_user[0]);
629 assert_eq!(expected_post_listing_no_user, read_post_listing_no_user);
630 assert_eq!(1, read_post_listings_no_user.len());
632 // assert_eq!(expected_post, inserted_post);
633 // assert_eq!(expected_post, updated_post);
634 assert_eq!(expected_post_like, inserted_post_like);
635 assert_eq!(1, like_removed);
636 assert_eq!(1, num_deleted);