2 aggregates::community_aggregates::CommunityAggregates,
4 community::{Community, CommunityFollower, CommunitySafe},
8 schema::{category, community, community_aggregates, community_follower, user_},
9 user::{UserSafe, User_},
14 use diesel::{result::Error, *};
17 #[derive(Debug, Serialize, Clone)]
18 pub struct CommunityView {
19 pub community: CommunitySafe,
20 pub creator: UserSafe,
21 pub category: Category,
23 pub counts: CommunityAggregates,
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);
35 let (community, creator, category, counts, subscribed) = community::table
37 .inner_join(user_::table)
38 .inner_join(category::table)
39 .inner_join(community_aggregates::table)
41 community_follower::table.on(
43 .eq(community_follower::community_id)
44 .and(community_follower::user_id.eq(user_id_join)),
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(),
59 Option<CommunityFollower>,
66 subscribed: subscribed.is_some(),
73 use crate::schema::{category, community, community_aggregates, community_follower, user_};
76 query_builder::BoxedSelectStatement,
77 query_source::joins::{Inner, Join, JoinOn, LeftOuter},
81 /// TODO awful, but necessary because of the boxed join
82 pub(super) type BoxedCommunityJoin<'a> = BoxedSelectStatement<
119 (Integer, Integer, BigInt, BigInt, BigInt),
120 Nullable<(Integer, Integer, Integer, Timestamp, Nullable<Bool>)>,
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>,
138 diesel::expression::operators::Eq<
139 diesel::expression::nullable::Nullable<community::columns::category_id>,
140 diesel::expression::nullable::Nullable<category::columns::id>,
143 community_aggregates::table,
146 diesel::expression::operators::Eq<
147 diesel::expression::nullable::Nullable<community_aggregates::columns::community_id>,
148 diesel::expression::nullable::Nullable<community::columns::id>,
151 community_follower::table,
154 diesel::expression::operators::And<
155 diesel::expression::operators::Eq<
156 community::columns::id,
157 community_follower::columns::community_id,
159 diesel::expression::operators::Eq<
160 community_follower::columns::user_id,
161 diesel::expression::bound::Bound<diesel::sql_types::Integer, i32>,
169 pub struct CommunityQueryBuilder<'a> {
170 conn: &'a PgConnection,
171 query: join_types::BoxedCommunityJoin<'a>,
174 search_term: Option<String>,
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);
184 let query = community::table
185 .inner_join(user_::table)
186 .inner_join(category::table)
187 .inner_join(community_aggregates::table)
189 community_follower::table.on(
191 .eq(community_follower::community_id)
192 .and(community_follower::user_id.eq(user_id_join)),
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(),
204 CommunityQueryBuilder {
207 sort: &SortType::Hot,
215 pub fn sort(mut self, sort: &'a SortType) -> Self {
220 pub fn show_nsfw(mut self, show_nsfw: bool) -> Self {
221 self.show_nsfw = show_nsfw;
225 pub fn search_term<T: MaybeOptional<String>>(mut self, search_term: T) -> Self {
226 self.search_term = search_term.get_optional();
230 pub fn page<T: MaybeOptional<i64>>(mut self, page: T) -> Self {
231 self.page = page.get_optional();
235 pub fn limit<T: MaybeOptional<i64>>(mut self, limit: T) -> Self {
236 self.limit = limit.get_optional();
240 pub fn list(self) -> Result<Vec<CommunityView>, Error> {
241 let mut query = self.query;
243 if let Some(search_term) = self.search_term {
244 let searcher = fuzzy_search(&search_term);
246 .filter(community::name.ilike(searcher.to_owned()))
247 .or_filter(community::title.ilike(searcher.to_owned()))
248 .or_filter(community::description.ilike(searcher));
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
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())
264 query = query.filter(community::nsfw.eq(false));
267 let (limit, offset) = limit_and_offset(self.page, self.limit);
271 .filter(community::removed.eq(false))
272 .filter(community::deleted.eq(false))
278 Option<CommunityFollower>,
291 Option<CommunityFollower>,
293 ) -> Vec<CommunityView> {
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(),
303 .collect::<Vec<CommunityView>>()