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