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