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