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