2 aggregates::community_aggregates::CommunityAggregates,
4 community::{Community, CommunityFollower, CommunitySafe},
7 schema::{category, community, community_aggregates, community_follower, user_},
8 user::{UserSafe, User_},
13 use diesel::{result::Error, *};
16 #[derive(Debug, Serialize, Clone)]
17 pub struct CommunityView {
18 pub community: CommunitySafe,
19 pub creator: UserSafe,
20 pub category: Category,
22 pub counts: CommunityAggregates,
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);
34 let (community, creator, category, counts, subscribed) = community::table
36 .inner_join(user_::table)
37 .inner_join(category::table)
38 .inner_join(community_aggregates::table)
40 community_follower::table.on(
42 .eq(community_follower::community_id)
43 .and(community_follower::user_id.eq(user_id_join)),
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(),
58 Option<CommunityFollower>,
65 subscribed: subscribed.is_some(),
72 use crate::schema::{category, community, community_aggregates, community_follower, user_};
75 query_builder::BoxedSelectStatement,
76 query_source::joins::{Inner, Join, JoinOn, LeftOuter},
80 /// TODO awful, but necessary because of the boxed join
81 pub(super) type BoxedCommunityJoin<'a> = BoxedSelectStatement<
118 (Integer, Integer, BigInt, BigInt, BigInt),
119 Nullable<(Integer, Integer, Integer, Timestamp, Nullable<Bool>)>,
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>,
137 diesel::expression::operators::Eq<
138 diesel::expression::nullable::Nullable<community::columns::category_id>,
139 diesel::expression::nullable::Nullable<category::columns::id>,
142 community_aggregates::table,
145 diesel::expression::operators::Eq<
146 diesel::expression::nullable::Nullable<community_aggregates::columns::community_id>,
147 diesel::expression::nullable::Nullable<community::columns::id>,
150 community_follower::table,
153 diesel::expression::operators::And<
154 diesel::expression::operators::Eq<
155 community::columns::id,
156 community_follower::columns::community_id,
158 diesel::expression::operators::Eq<
159 community_follower::columns::user_id,
160 diesel::expression::bound::Bound<diesel::sql_types::Integer, i32>,
168 pub struct CommunityQueryBuilder<'a> {
169 conn: &'a PgConnection,
170 query: join_types::BoxedCommunityJoin<'a>,
173 search_term: Option<String>,
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);
183 let query = community::table
184 .inner_join(user_::table)
185 .inner_join(category::table)
186 .inner_join(community_aggregates::table)
188 community_follower::table.on(
190 .eq(community_follower::community_id)
191 .and(community_follower::user_id.eq(user_id_join)),
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(),
203 CommunityQueryBuilder {
206 sort: &SortType::Hot,
214 pub fn sort(mut self, sort: &'a SortType) -> Self {
219 pub fn show_nsfw(mut self, show_nsfw: bool) -> Self {
220 self.show_nsfw = show_nsfw;
224 pub fn search_term<T: MaybeOptional<String>>(mut self, search_term: T) -> Self {
225 self.search_term = search_term.get_optional();
229 pub fn page<T: MaybeOptional<i64>>(mut self, page: T) -> Self {
230 self.page = page.get_optional();
234 pub fn limit<T: MaybeOptional<i64>>(mut self, limit: T) -> Self {
235 self.limit = limit.get_optional();
239 pub fn list(self) -> Result<Vec<CommunityView>, Error> {
240 let mut query = self.query;
242 if let Some(search_term) = self.search_term {
243 let searcher = fuzzy_search(&search_term);
245 .filter(community::name.ilike(searcher.to_owned()))
246 .or_filter(community::title.ilike(searcher.to_owned()))
247 .or_filter(community::description.ilike(searcher));
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
256 // TODO do custom sql function for hot_rank
257 // .order_by(hot_rank.desc())
258 .then_order_by(community_aggregates::subscribers.desc())
263 query = query.filter(community::nsfw.eq(false));
266 let (limit, offset) = limit_and_offset(self.page, self.limit);
270 .filter(community::removed.eq(false))
271 .filter(community::deleted.eq(false))
277 Option<CommunityFollower>,
290 Option<CommunityFollower>,
292 ) -> Vec<CommunityView> {
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(),
302 .collect::<Vec<CommunityView>>()