]> Untitled Git - lemmy.git/blob - crates/db_views_actor/src/community_view.rs
Fixing removed posts showing. Fixes #2875 (#3279)
[lemmy.git] / crates / db_views_actor / src / community_view.rs
1 use crate::structs::{CommunityModeratorView, CommunityView, PersonView};
2 use diesel::{
3   result::Error,
4   BoolExpressionMethods,
5   ExpressionMethods,
6   JoinOnDsl,
7   NullableExpressionMethods,
8   PgTextExpressionMethods,
9   QueryDsl,
10 };
11 use diesel_async::RunQueryDsl;
12 use lemmy_db_schema::{
13   aggregates::structs::CommunityAggregates,
14   newtypes::{CommunityId, PersonId},
15   schema::{community, community_aggregates, community_block, community_follower, local_user},
16   source::{
17     community::{Community, CommunityFollower},
18     community_block::CommunityBlock,
19     local_user::LocalUser,
20   },
21   traits::JoinView,
22   utils::{fuzzy_search, get_conn, limit_and_offset, DbPool},
23   ListingType,
24   SortType,
25 };
26 use typed_builder::TypedBuilder;
27
28 type CommunityViewTuple = (
29   Community,
30   CommunityAggregates,
31   Option<CommunityFollower>,
32   Option<CommunityBlock>,
33 );
34
35 impl CommunityView {
36   pub async fn read(
37     pool: &DbPool,
38     community_id: CommunityId,
39     my_person_id: Option<PersonId>,
40     is_mod_or_admin: Option<bool>,
41   ) -> Result<Self, Error> {
42     let conn = &mut get_conn(pool).await?;
43     // The left join below will return None in this case
44     let person_id_join = my_person_id.unwrap_or(PersonId(-1));
45
46     let mut query = community::table
47       .find(community_id)
48       .inner_join(community_aggregates::table)
49       .left_join(
50         community_follower::table.on(
51           community::id
52             .eq(community_follower::community_id)
53             .and(community_follower::person_id.eq(person_id_join)),
54         ),
55       )
56       .left_join(
57         community_block::table.on(
58           community::id
59             .eq(community_block::community_id)
60             .and(community_block::person_id.eq(person_id_join)),
61         ),
62       )
63       .select((
64         community::all_columns,
65         community_aggregates::all_columns,
66         community_follower::all_columns.nullable(),
67         community_block::all_columns.nullable(),
68       ))
69       .into_boxed();
70
71     // Hide deleted and removed for non-admins or mods
72     if !is_mod_or_admin.unwrap_or(false) {
73       query = query
74         .filter(community::removed.eq(false))
75         .filter(community::deleted.eq(false));
76     }
77
78     let (community, counts, follower, blocked) = query.first::<CommunityViewTuple>(conn).await?;
79
80     Ok(CommunityView {
81       community,
82       subscribed: CommunityFollower::to_subscribed_type(&follower),
83       blocked: blocked.is_some(),
84       counts,
85     })
86   }
87
88   pub async fn is_mod_or_admin(
89     pool: &DbPool,
90     person_id: PersonId,
91     community_id: CommunityId,
92   ) -> Result<bool, Error> {
93     let is_mod = CommunityModeratorView::for_community(pool, community_id)
94       .await
95       .map(|v| {
96         v.into_iter()
97           .map(|m| m.moderator.id)
98           .collect::<Vec<PersonId>>()
99       })
100       .unwrap_or_default()
101       .contains(&person_id);
102     if is_mod {
103       return Ok(true);
104     }
105
106     let is_admin = PersonView::admins(pool)
107       .await
108       .map(|v| {
109         v.into_iter()
110           .map(|a| a.person.id)
111           .collect::<Vec<PersonId>>()
112       })
113       .unwrap_or_default()
114       .contains(&person_id);
115     Ok(is_admin)
116   }
117 }
118
119 #[derive(TypedBuilder)]
120 #[builder(field_defaults(default))]
121 pub struct CommunityQuery<'a> {
122   #[builder(!default)]
123   pool: &'a DbPool,
124   listing_type: Option<ListingType>,
125   sort: Option<SortType>,
126   local_user: Option<&'a LocalUser>,
127   search_term: Option<String>,
128   is_mod_or_admin: Option<bool>,
129   page: Option<i64>,
130   limit: Option<i64>,
131 }
132
133 impl<'a> CommunityQuery<'a> {
134   pub async fn list(self) -> Result<Vec<CommunityView>, Error> {
135     let conn = &mut get_conn(self.pool).await?;
136
137     // The left join below will return None in this case
138     let person_id_join = self.local_user.map(|l| l.person_id).unwrap_or(PersonId(-1));
139
140     let mut query = community::table
141       .inner_join(community_aggregates::table)
142       .left_join(local_user::table.on(local_user::person_id.eq(person_id_join)))
143       .left_join(
144         community_follower::table.on(
145           community::id
146             .eq(community_follower::community_id)
147             .and(community_follower::person_id.eq(person_id_join)),
148         ),
149       )
150       .left_join(
151         community_block::table.on(
152           community::id
153             .eq(community_block::community_id)
154             .and(community_block::person_id.eq(person_id_join)),
155         ),
156       )
157       .select((
158         community::all_columns,
159         community_aggregates::all_columns,
160         community_follower::all_columns.nullable(),
161         community_block::all_columns.nullable(),
162       ))
163       .into_boxed();
164
165     if let Some(search_term) = self.search_term {
166       let searcher = fuzzy_search(&search_term);
167       query = query
168         .filter(community::name.ilike(searcher.clone()))
169         .or_filter(community::title.ilike(searcher));
170     };
171
172     // Hide deleted and removed for non-admins or mods
173     if !self.is_mod_or_admin.unwrap_or(false) {
174       query = query
175         .filter(community::removed.eq(false))
176         .filter(community::deleted.eq(false))
177         .filter(
178           community::hidden
179             .eq(false)
180             .or(community_follower::person_id.eq(person_id_join)),
181         );
182     }
183
184     match self.sort.unwrap_or(SortType::Hot) {
185       SortType::New => query = query.order_by(community::published.desc()),
186       SortType::TopAll => query = query.order_by(community_aggregates::subscribers.desc()),
187       SortType::TopMonth => query = query.order_by(community_aggregates::users_active_month.desc()),
188       SortType::Hot => query = query.order_by(community_aggregates::hot_rank.desc()),
189       // Covers all other sorts
190       _ => query = query.order_by(community_aggregates::users_active_month.desc()),
191     };
192
193     if let Some(listing_type) = self.listing_type {
194       query = match listing_type {
195         ListingType::Subscribed => query.filter(community_follower::person_id.is_not_null()), // TODO could be this: and(community_follower::person_id.eq(person_id_join)),
196         ListingType::Local => query.filter(community::local.eq(true)),
197         _ => query,
198       };
199     }
200
201     // Don't show blocked communities or nsfw communities if not enabled in profile
202     if self.local_user.is_some() {
203       query = query.filter(community_block::person_id.is_null());
204       query = query.filter(community::nsfw.eq(false).or(local_user::show_nsfw.eq(true)));
205     } else {
206       // No person in request, only show nsfw communities if show_nsfw passed into request
207       if !self.local_user.map(|l| l.show_nsfw).unwrap_or(false) {
208         query = query.filter(community::nsfw.eq(false));
209       }
210     }
211
212     let (limit, offset) = limit_and_offset(self.page, self.limit)?;
213     let res = query
214       .limit(limit)
215       .offset(offset)
216       .load::<CommunityViewTuple>(conn)
217       .await?;
218
219     Ok(res.into_iter().map(CommunityView::from_tuple).collect())
220   }
221 }
222
223 impl JoinView for CommunityView {
224   type JoinTuple = CommunityViewTuple;
225   fn from_tuple(a: Self::JoinTuple) -> Self {
226     Self {
227       community: a.0,
228       counts: a.1,
229       subscribed: CommunityFollower::to_subscribed_type(&a.2),
230       blocked: a.3.is_some(),
231     }
232   }
233 }