]> Untitled Git - lemmy.git/blob - crates/db_views_actor/src/community_view.rs
Reduce amount of columns selected (#3755)
[lemmy.git] / crates / db_views_actor / src / community_view.rs
1 use crate::structs::{CommunityModeratorView, CommunityView, PersonView};
2 use diesel::{
3   pg::Pg,
4   result::Error,
5   BoolExpressionMethods,
6   ExpressionMethods,
7   JoinOnDsl,
8   NullableExpressionMethods,
9   PgTextExpressionMethods,
10   QueryDsl,
11 };
12 use diesel_async::RunQueryDsl;
13 use lemmy_db_schema::{
14   aggregates::structs::CommunityAggregates,
15   newtypes::{CommunityId, PersonId},
16   schema::{community, community_aggregates, community_block, community_follower, local_user},
17   source::{
18     community::{Community, CommunityFollower},
19     local_user::LocalUser,
20   },
21   traits::JoinView,
22   utils::{fuzzy_search, limit_and_offset, DbConn, DbPool, ListFn, Queries, ReadFn},
23   ListingType,
24   SortType,
25   SubscribedType,
26 };
27
28 type CommunityViewTuple = (Community, CommunityAggregates, SubscribedType, bool);
29
30 fn queries<'a>() -> Queries<
31   impl ReadFn<'a, CommunityView, (CommunityId, Option<PersonId>, Option<bool>)>,
32   impl ListFn<'a, CommunityView, CommunityQuery<'a>>,
33 > {
34   let all_joins = |query: community::BoxedQuery<'a, Pg>, my_person_id: Option<PersonId>| {
35     // The left join below will return None in this case
36     let person_id_join = my_person_id.unwrap_or(PersonId(-1));
37
38     query
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::person_id.eq(person_id_join)),
45         ),
46       )
47       .left_join(
48         community_block::table.on(
49           community::id
50             .eq(community_block::community_id)
51             .and(community_block::person_id.eq(person_id_join)),
52         ),
53       )
54   };
55
56   let selection = (
57     community::all_columns,
58     community_aggregates::all_columns,
59     CommunityFollower::select_subscribed_type(),
60     community_block::id.nullable().is_not_null(),
61   );
62
63   let not_removed_or_deleted = community::removed
64     .eq(false)
65     .and(community::deleted.eq(false));
66
67   let read = move |mut conn: DbConn<'a>,
68                    (community_id, my_person_id, is_mod_or_admin): (
69     CommunityId,
70     Option<PersonId>,
71     Option<bool>,
72   )| async move {
73     let mut query = all_joins(
74       community::table.find(community_id).into_boxed(),
75       my_person_id,
76     )
77     .select(selection);
78
79     // Hide deleted and removed for non-admins or mods
80     if !is_mod_or_admin.unwrap_or(false) {
81       query = query.filter(not_removed_or_deleted);
82     }
83
84     query.first::<CommunityViewTuple>(&mut conn).await
85   };
86
87   let list = move |mut conn: DbConn<'a>, options: CommunityQuery<'a>| async move {
88     use SortType::*;
89
90     let my_person_id = options.local_user.map(|l| l.person_id);
91
92     // The left join below will return None in this case
93     let person_id_join = my_person_id.unwrap_or(PersonId(-1));
94
95     let mut query = all_joins(community::table.into_boxed(), my_person_id)
96       .left_join(local_user::table.on(local_user::person_id.eq(person_id_join)))
97       .select(selection);
98
99     if let Some(search_term) = options.search_term {
100       let searcher = fuzzy_search(&search_term);
101       query = query
102         .filter(community::name.ilike(searcher.clone()))
103         .or_filter(community::title.ilike(searcher))
104     }
105
106     // Hide deleted and removed for non-admins or mods
107     if !options.is_mod_or_admin.unwrap_or(false) {
108       query = query.filter(not_removed_or_deleted).filter(
109         community::hidden
110           .eq(false)
111           .or(community_follower::person_id.eq(person_id_join)),
112       );
113     }
114
115     match options.sort.unwrap_or(Hot) {
116       Hot | Active => query = query.order_by(community_aggregates::hot_rank.desc()),
117       NewComments | TopDay | TopTwelveHour | TopSixHour | TopHour => {
118         query = query.order_by(community_aggregates::users_active_day.desc())
119       }
120       New => query = query.order_by(community::published.desc()),
121       Old => query = query.order_by(community::published.asc()),
122       // Controversial is temporary until a CommentSortType is created
123       MostComments | Controversial => query = query.order_by(community_aggregates::comments.desc()),
124       TopAll | TopYear | TopNineMonths => {
125         query = query.order_by(community_aggregates::subscribers.desc())
126       }
127       TopSixMonths | TopThreeMonths => {
128         query = query.order_by(community_aggregates::users_active_half_year.desc())
129       }
130       TopMonth => query = query.order_by(community_aggregates::users_active_month.desc()),
131       TopWeek => query = query.order_by(community_aggregates::users_active_week.desc()),
132     };
133
134     if let Some(listing_type) = options.listing_type {
135       query = match listing_type {
136         ListingType::Subscribed => query.filter(community_follower::pending.is_not_null()), // TODO could be this: and(community_follower::person_id.eq(person_id_join)),
137         ListingType::Local => query.filter(community::local.eq(true)),
138         _ => query,
139       };
140     }
141
142     // Don't show blocked communities or nsfw communities if not enabled in profile
143     if options.local_user.is_some() {
144       query = query.filter(community_block::person_id.is_null());
145       query = query.filter(community::nsfw.eq(false).or(local_user::show_nsfw.eq(true)));
146     } else {
147       // No person in request, only show nsfw communities if show_nsfw is passed into request
148       if !options.show_nsfw.unwrap_or(false) {
149         query = query.filter(community::nsfw.eq(false));
150       }
151     }
152
153     let (limit, offset) = limit_and_offset(options.page, options.limit)?;
154     query
155       .limit(limit)
156       .offset(offset)
157       .load::<CommunityViewTuple>(&mut conn)
158       .await
159   };
160
161   Queries::new(read, list)
162 }
163
164 impl CommunityView {
165   pub async fn read(
166     pool: &mut DbPool<'_>,
167     community_id: CommunityId,
168     my_person_id: Option<PersonId>,
169     is_mod_or_admin: Option<bool>,
170   ) -> Result<Self, Error> {
171     queries()
172       .read(pool, (community_id, my_person_id, is_mod_or_admin))
173       .await
174   }
175
176   pub async fn is_mod_or_admin(
177     pool: &mut DbPool<'_>,
178     person_id: PersonId,
179     community_id: CommunityId,
180   ) -> Result<bool, Error> {
181     let is_mod =
182       CommunityModeratorView::is_community_moderator(pool, community_id, person_id).await?;
183     if is_mod {
184       return Ok(true);
185     }
186
187     PersonView::is_admin(pool, person_id).await
188   }
189 }
190
191 #[derive(Default)]
192 pub struct CommunityQuery<'a> {
193   pub listing_type: Option<ListingType>,
194   pub sort: Option<SortType>,
195   pub local_user: Option<&'a LocalUser>,
196   pub search_term: Option<String>,
197   pub is_mod_or_admin: Option<bool>,
198   pub show_nsfw: Option<bool>,
199   pub page: Option<i64>,
200   pub limit: Option<i64>,
201 }
202
203 impl<'a> CommunityQuery<'a> {
204   pub async fn list(self, pool: &mut DbPool<'_>) -> Result<Vec<CommunityView>, Error> {
205     queries().list(pool, self).await
206   }
207 }
208
209 impl JoinView for CommunityView {
210   type JoinTuple = CommunityViewTuple;
211   fn from_tuple(a: Self::JoinTuple) -> Self {
212     Self {
213       community: a.0,
214       counts: a.1,
215       subscribed: a.2,
216       blocked: a.3,
217     }
218   }
219 }