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