]> Untitled Git - lemmy.git/blob - lemmy_db/src/views/community_view.rs
2ab351f400090cca7c93da39584c4602ddbae142
[lemmy.git] / lemmy_db / src / views / community_view.rs
1 use crate::{
2   aggregates::community_aggregates::CommunityAggregates,
3   category::Category,
4   community::{Community, CommunityFollower, CommunitySafe},
5   fuzzy_search,
6   limit_and_offset,
7   schema::{category, community, community_aggregates, community_follower, user_},
8   user::{UserSafe, User_},
9   MaybeOptional,
10   SortType,
11   ToSafe,
12 };
13 use diesel::{result::Error, *};
14 use serde::Serialize;
15
16 #[derive(Debug, Serialize, Clone)]
17 pub struct CommunityView {
18   pub community: CommunitySafe,
19   pub creator: UserSafe,
20   pub category: Category,
21   pub subscribed: bool,
22   pub counts: CommunityAggregates,
23 }
24
25 impl CommunityView {
26   pub fn read(
27     conn: &PgConnection,
28     community_id: i32,
29     my_user_id: Option<i32>,
30   ) -> Result<Self, Error> {
31     // The left join below will return None in this case
32     let user_id_join = my_user_id.unwrap_or(-1);
33
34     let (community, creator, category, counts, subscribed) = community::table
35       .find(community_id)
36       .inner_join(user_::table)
37       .inner_join(category::table)
38       .inner_join(community_aggregates::table)
39       .left_join(
40         community_follower::table.on(
41           community::id
42             .eq(community_follower::community_id)
43             .and(community_follower::user_id.eq(user_id_join)),
44         ),
45       )
46       .select((
47         Community::safe_columns_tuple(),
48         User_::safe_columns_tuple(),
49         category::all_columns,
50         community_aggregates::all_columns,
51         community_follower::all_columns.nullable(),
52       ))
53       .first::<(
54         CommunitySafe,
55         UserSafe,
56         Category,
57         CommunityAggregates,
58         Option<CommunityFollower>,
59       )>(conn)?;
60
61     Ok(CommunityView {
62       community,
63       creator,
64       category,
65       subscribed: subscribed.is_some(),
66       counts,
67     })
68   }
69 }
70
71 mod join_types {
72   use crate::schema::{category, community, community_aggregates, community_follower, user_};
73   use diesel::{
74     pg::Pg,
75     query_builder::BoxedSelectStatement,
76     query_source::joins::{Inner, Join, JoinOn, LeftOuter},
77     sql_types::*,
78   };
79
80   /// TODO awful, but necessary because of the boxed join
81   pub(super) type BoxedCommunityJoin<'a> = BoxedSelectStatement<
82     'a,
83     (
84       (
85         Integer,
86         Text,
87         Text,
88         Nullable<Text>,
89         Integer,
90         Integer,
91         Bool,
92         Timestamp,
93         Nullable<Timestamp>,
94         Bool,
95         Bool,
96         Text,
97         Bool,
98         Nullable<Text>,
99         Nullable<Text>,
100       ),
101       (
102         Integer,
103         Text,
104         Nullable<Text>,
105         Nullable<Text>,
106         Bool,
107         Bool,
108         Timestamp,
109         Nullable<Timestamp>,
110         Nullable<Text>,
111         Text,
112         Nullable<Text>,
113         Bool,
114         Nullable<Text>,
115         Bool,
116       ),
117       (Integer, Text),
118       (Integer, Integer, BigInt, BigInt, BigInt),
119       Nullable<(Integer, Integer, Integer, Timestamp, Nullable<Bool>)>,
120     ),
121     JoinOn<
122       Join<
123         JoinOn<
124           Join<
125             JoinOn<
126               Join<
127                 JoinOn<
128                   Join<community::table, user_::table, Inner>,
129                   diesel::expression::operators::Eq<
130                     diesel::expression::nullable::Nullable<community::columns::creator_id>,
131                     diesel::expression::nullable::Nullable<user_::columns::id>,
132                   >,
133                 >,
134                 category::table,
135                 Inner,
136               >,
137               diesel::expression::operators::Eq<
138                 diesel::expression::nullable::Nullable<community::columns::category_id>,
139                 diesel::expression::nullable::Nullable<category::columns::id>,
140               >,
141             >,
142             community_aggregates::table,
143             Inner,
144           >,
145           diesel::expression::operators::Eq<
146             diesel::expression::nullable::Nullable<community_aggregates::columns::community_id>,
147             diesel::expression::nullable::Nullable<community::columns::id>,
148           >,
149         >,
150         community_follower::table,
151         LeftOuter,
152       >,
153       diesel::expression::operators::And<
154         diesel::expression::operators::Eq<
155           community::columns::id,
156           community_follower::columns::community_id,
157         >,
158         diesel::expression::operators::Eq<
159           community_follower::columns::user_id,
160           diesel::expression::bound::Bound<diesel::sql_types::Integer, i32>,
161         >,
162       >,
163     >,
164     Pg,
165   >;
166 }
167
168 pub struct CommunityQueryBuilder<'a> {
169   conn: &'a PgConnection,
170   query: join_types::BoxedCommunityJoin<'a>,
171   sort: &'a SortType,
172   show_nsfw: bool,
173   search_term: Option<String>,
174   page: Option<i64>,
175   limit: Option<i64>,
176 }
177
178 impl<'a> CommunityQueryBuilder<'a> {
179   pub fn create(conn: &'a PgConnection, my_user_id: Option<i32>) -> Self {
180     // The left join below will return None in this case
181     let user_id_join = my_user_id.unwrap_or(-1);
182
183     let query = community::table
184       .inner_join(user_::table)
185       .inner_join(category::table)
186       .inner_join(community_aggregates::table)
187       .left_join(
188         community_follower::table.on(
189           community::id
190             .eq(community_follower::community_id)
191             .and(community_follower::user_id.eq(user_id_join)),
192         ),
193       )
194       .select((
195         Community::safe_columns_tuple(),
196         User_::safe_columns_tuple(),
197         category::all_columns,
198         community_aggregates::all_columns,
199         community_follower::all_columns.nullable(),
200       ))
201       .into_boxed();
202
203     CommunityQueryBuilder {
204       conn,
205       query,
206       sort: &SortType::Hot,
207       show_nsfw: true,
208       search_term: None,
209       page: None,
210       limit: None,
211     }
212   }
213
214   pub fn sort(mut self, sort: &'a SortType) -> Self {
215     self.sort = sort;
216     self
217   }
218
219   pub fn show_nsfw(mut self, show_nsfw: bool) -> Self {
220     self.show_nsfw = show_nsfw;
221     self
222   }
223
224   pub fn search_term<T: MaybeOptional<String>>(mut self, search_term: T) -> Self {
225     self.search_term = search_term.get_optional();
226     self
227   }
228
229   pub fn page<T: MaybeOptional<i64>>(mut self, page: T) -> Self {
230     self.page = page.get_optional();
231     self
232   }
233
234   pub fn limit<T: MaybeOptional<i64>>(mut self, limit: T) -> Self {
235     self.limit = limit.get_optional();
236     self
237   }
238
239   pub fn list(self) -> Result<Vec<CommunityView>, Error> {
240     let mut query = self.query;
241
242     if let Some(search_term) = self.search_term {
243       let searcher = fuzzy_search(&search_term);
244       query = query
245         .filter(community::name.ilike(searcher.to_owned()))
246         .or_filter(community::title.ilike(searcher.to_owned()))
247         .or_filter(community::description.ilike(searcher));
248     };
249
250     match self.sort {
251       SortType::New => query = query.order_by(community::published.desc()),
252       SortType::TopAll => query = query.order_by(community_aggregates::subscribers.desc()),
253       // Covers all other sorts, including hot
254       _ => {
255         query = query
256           // TODO do custom sql function for hot_rank
257           // .order_by(hot_rank.desc())
258           .then_order_by(community_aggregates::subscribers.desc())
259       }
260     };
261
262     if !self.show_nsfw {
263       query = query.filter(community::nsfw.eq(false));
264     };
265
266     let (limit, offset) = limit_and_offset(self.page, self.limit);
267     let res = query
268       .limit(limit)
269       .offset(offset)
270       .filter(community::removed.eq(false))
271       .filter(community::deleted.eq(false))
272       .load::<(
273         CommunitySafe,
274         UserSafe,
275         Category,
276         CommunityAggregates,
277         Option<CommunityFollower>,
278       )>(self.conn)?;
279
280     Ok(to_vec(res))
281   }
282 }
283
284 fn to_vec(
285   users: Vec<(
286     CommunitySafe,
287     UserSafe,
288     Category,
289     CommunityAggregates,
290     Option<CommunityFollower>,
291   )>,
292 ) -> Vec<CommunityView> {
293   users
294     .iter()
295     .map(|a| CommunityView {
296       community: a.0.to_owned(),
297       creator: a.1.to_owned(),
298       category: a.2.to_owned(),
299       counts: a.3.to_owned(),
300       subscribed: a.4.is_some(),
301     })
302     .collect::<Vec<CommunityView>>()
303 }