]> Untitled Git - lemmy.git/blob - crates/db_views_actor/src/community_view.rs
9d48f17d64501569d37d5a45056e4a7bd8252295
[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(true) {
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(true) {
174       query = query
175         .filter(community::removed.eq(false))
176         .filter(community::deleted.eq(false));
177     }
178
179     match self.sort.unwrap_or(SortType::Hot) {
180       SortType::New => query = query.order_by(community::published.desc()),
181       SortType::TopAll => query = query.order_by(community_aggregates::subscribers.desc()),
182       SortType::TopMonth => query = query.order_by(community_aggregates::users_active_month.desc()),
183       SortType::Hot => {
184         query = query.order_by(community_aggregates::users_active_month.desc());
185         // Don't show hidden communities in Hot (trending)
186         query = query.filter(
187           community::hidden
188             .eq(false)
189             .or(community_follower::person_id.eq(person_id_join)),
190         );
191       }
192       // Covers all other sorts
193       _ => query = query.order_by(community_aggregates::users_active_month.desc()),
194     };
195
196     if let Some(listing_type) = self.listing_type {
197       query = match listing_type {
198         ListingType::Subscribed => query.filter(community_follower::person_id.is_not_null()), // TODO could be this: and(community_follower::person_id.eq(person_id_join)),
199         ListingType::Local => query.filter(community::local.eq(true)),
200         _ => query,
201       };
202     }
203
204     // Don't show blocked communities or nsfw communities if not enabled in profile
205     if self.local_user.is_some() {
206       query = query.filter(community_block::person_id.is_null());
207       query = query.filter(community::nsfw.eq(false).or(local_user::show_nsfw.eq(true)));
208     } else {
209       // No person in request, only show nsfw communities if show_nsfw passed into request
210       if !self.local_user.map(|l| l.show_nsfw).unwrap_or(false) {
211         query = query.filter(community::nsfw.eq(false));
212       }
213     }
214
215     let (limit, offset) = limit_and_offset(self.page, self.limit)?;
216     let res = query
217       .limit(limit)
218       .offset(offset)
219       .filter(community::removed.eq(false))
220       .filter(community::deleted.eq(false))
221       .load::<CommunityViewTuple>(conn)
222       .await?;
223
224     Ok(res.into_iter().map(CommunityView::from_tuple).collect())
225   }
226 }
227
228 impl JoinView for CommunityView {
229   type JoinTuple = CommunityViewTuple;
230   fn from_tuple(a: Self::JoinTuple) -> Self {
231     Self {
232       community: a.0,
233       counts: a.1,
234       subscribed: CommunityFollower::to_subscribed_type(&a.2),
235       blocked: a.3.is_some(),
236     }
237   }
238 }