]> Untitled Git - lemmy.git/blob - crates/db_views_actor/src/community_view.rs
Move most code into crates/ subfolder
[lemmy.git] / crates / db_views_actor / src / community_view.rs
1 use crate::{community_moderator_view::CommunityModeratorView, user_view::UserViewSafe};
2 use diesel::{result::Error, *};
3 use lemmy_db_queries::{
4   aggregates::community_aggregates::CommunityAggregates,
5   functions::hot_rank,
6   fuzzy_search,
7   limit_and_offset,
8   MaybeOptional,
9   SortType,
10   ToSafe,
11   ViewToVec,
12 };
13 use lemmy_db_schema::{
14   schema::{category, community, community_aggregates, community_follower, user_},
15   source::{
16     category::Category,
17     community::{Community, CommunityFollower, CommunitySafe},
18     user::{UserSafe, User_},
19   },
20 };
21 use serde::Serialize;
22
23 #[derive(Debug, Serialize, Clone)]
24 pub struct CommunityView {
25   pub community: CommunitySafe,
26   pub creator: UserSafe,
27   pub category: Category,
28   pub subscribed: bool,
29   pub counts: CommunityAggregates,
30 }
31
32 type CommunityViewTuple = (
33   CommunitySafe,
34   UserSafe,
35   Category,
36   CommunityAggregates,
37   Option<CommunityFollower>,
38 );
39
40 impl CommunityView {
41   pub fn read(
42     conn: &PgConnection,
43     community_id: i32,
44     my_user_id: Option<i32>,
45   ) -> Result<Self, Error> {
46     // The left join below will return None in this case
47     let user_id_join = my_user_id.unwrap_or(-1);
48
49     let (community, creator, category, counts, follower) = community::table
50       .find(community_id)
51       .inner_join(user_::table)
52       .inner_join(category::table)
53       .inner_join(community_aggregates::table)
54       .left_join(
55         community_follower::table.on(
56           community::id
57             .eq(community_follower::community_id)
58             .and(community_follower::user_id.eq(user_id_join)),
59         ),
60       )
61       .select((
62         Community::safe_columns_tuple(),
63         User_::safe_columns_tuple(),
64         category::all_columns,
65         community_aggregates::all_columns,
66         community_follower::all_columns.nullable(),
67       ))
68       .first::<CommunityViewTuple>(conn)?;
69
70     Ok(CommunityView {
71       community,
72       creator,
73       category,
74       subscribed: follower.is_some(),
75       counts,
76     })
77   }
78
79   // TODO: this function is only used by is_mod_or_admin() below, can probably be merged
80   fn community_mods_and_admins(conn: &PgConnection, community_id: i32) -> Result<Vec<i32>, Error> {
81     let mut mods_and_admins: Vec<i32> = Vec::new();
82     mods_and_admins.append(
83       &mut CommunityModeratorView::for_community(conn, community_id)
84         .map(|v| v.into_iter().map(|m| m.moderator.id).collect())?,
85     );
86     mods_and_admins
87       .append(&mut UserViewSafe::admins(conn).map(|v| v.into_iter().map(|a| a.user.id).collect())?);
88     Ok(mods_and_admins)
89   }
90
91   pub fn is_mod_or_admin(conn: &PgConnection, user_id: i32, community_id: i32) -> bool {
92     Self::community_mods_and_admins(conn, community_id)
93       .unwrap_or_default()
94       .contains(&user_id)
95   }
96 }
97
98 pub struct CommunityQueryBuilder<'a> {
99   conn: &'a PgConnection,
100   sort: &'a SortType,
101   my_user_id: Option<i32>,
102   show_nsfw: bool,
103   search_term: Option<String>,
104   page: Option<i64>,
105   limit: Option<i64>,
106 }
107
108 impl<'a> CommunityQueryBuilder<'a> {
109   pub fn create(conn: &'a PgConnection) -> Self {
110     CommunityQueryBuilder {
111       conn,
112       my_user_id: None,
113       sort: &SortType::Hot,
114       show_nsfw: true,
115       search_term: None,
116       page: None,
117       limit: None,
118     }
119   }
120
121   pub fn sort(mut self, sort: &'a SortType) -> Self {
122     self.sort = sort;
123     self
124   }
125
126   pub fn show_nsfw(mut self, show_nsfw: bool) -> Self {
127     self.show_nsfw = show_nsfw;
128     self
129   }
130
131   pub fn search_term<T: MaybeOptional<String>>(mut self, search_term: T) -> Self {
132     self.search_term = search_term.get_optional();
133     self
134   }
135
136   pub fn my_user_id<T: MaybeOptional<i32>>(mut self, my_user_id: T) -> Self {
137     self.my_user_id = my_user_id.get_optional();
138     self
139   }
140
141   pub fn page<T: MaybeOptional<i64>>(mut self, page: T) -> Self {
142     self.page = page.get_optional();
143     self
144   }
145
146   pub fn limit<T: MaybeOptional<i64>>(mut self, limit: T) -> Self {
147     self.limit = limit.get_optional();
148     self
149   }
150
151   pub fn list(self) -> Result<Vec<CommunityView>, Error> {
152     // The left join below will return None in this case
153     let user_id_join = self.my_user_id.unwrap_or(-1);
154
155     let mut query = community::table
156       .inner_join(user_::table)
157       .inner_join(category::table)
158       .inner_join(community_aggregates::table)
159       .left_join(
160         community_follower::table.on(
161           community::id
162             .eq(community_follower::community_id)
163             .and(community_follower::user_id.eq(user_id_join)),
164         ),
165       )
166       .select((
167         Community::safe_columns_tuple(),
168         User_::safe_columns_tuple(),
169         category::all_columns,
170         community_aggregates::all_columns,
171         community_follower::all_columns.nullable(),
172       ))
173       .into_boxed();
174
175     if let Some(search_term) = self.search_term {
176       let searcher = fuzzy_search(&search_term);
177       query = query
178         .filter(community::name.ilike(searcher.to_owned()))
179         .or_filter(community::title.ilike(searcher.to_owned()))
180         .or_filter(community::description.ilike(searcher));
181     };
182
183     match self.sort {
184       SortType::New => query = query.order_by(community::published.desc()),
185       SortType::TopAll => query = query.order_by(community_aggregates::subscribers.desc()),
186       // Covers all other sorts, including hot
187       _ => {
188         query = query
189           .order_by(
190             hot_rank(
191               community_aggregates::subscribers,
192               community_aggregates::published,
193             )
194             .desc(),
195           )
196           .then_order_by(community_aggregates::published.desc())
197       }
198     };
199
200     if !self.show_nsfw {
201       query = query.filter(community::nsfw.eq(false));
202     };
203
204     let (limit, offset) = limit_and_offset(self.page, self.limit);
205     let res = query
206       .limit(limit)
207       .offset(offset)
208       .filter(community::removed.eq(false))
209       .filter(community::deleted.eq(false))
210       .load::<CommunityViewTuple>(self.conn)?;
211
212     Ok(CommunityView::from_tuple_to_vec(res))
213   }
214 }
215
216 impl ViewToVec for CommunityView {
217   type DbTuple = CommunityViewTuple;
218   fn from_tuple_to_vec(items: Vec<Self::DbTuple>) -> Vec<Self> {
219     items
220       .iter()
221       .map(|a| Self {
222         community: a.0.to_owned(),
223         creator: a.1.to_owned(),
224         category: a.2.to_owned(),
225         counts: a.3.to_owned(),
226         subscribed: a.4.is_some(),
227       })
228       .collect::<Vec<Self>>()
229   }
230 }