1 use actix_web::{web, web::Data};
2 use lemmy_api_structs::{
11 use lemmy_db_queries::{
13 community::{CommunityModerator_, Community_},
19 use lemmy_db_schema::source::{
20 community::{Community, CommunityModerator},
24 use lemmy_db_views::local_user_view::{LocalUserSettingsView, LocalUserView};
25 use lemmy_db_views_actor::{
26 community_person_ban_view::CommunityPersonBanView,
27 community_view::CommunityView,
31 settings::structs::Settings,
36 use lemmy_websocket::{serialize_websocket_message, LemmyContext, UserOperation};
37 use serde::Deserialize;
38 use std::process::Command;
49 #[async_trait::async_trait(?Send)]
51 type Response: serde::ser::Serialize + Send;
55 context: &Data<LemmyContext>,
56 websocket_id: Option<ConnectionId>,
57 ) -> Result<Self::Response, LemmyError>;
60 pub(crate) async fn is_mod_or_admin(
64 ) -> Result<(), LemmyError> {
65 let is_mod_or_admin = blocking(pool, move |conn| {
66 CommunityView::is_mod_or_admin(conn, person_id, community_id)
70 return Err(ApiError::err("not_a_mod_or_admin").into());
75 // TODO this probably isn't necessary anymore
76 // pub async fn is_admin(pool: &DbPool, person_id: i32) -> Result<(), LemmyError> {
77 // let user = blocking(pool, move |conn| LocalUser::read(conn, person_id)).await??;
79 // return Err(ApiError::err("not_an_admin").into());
84 pub fn is_admin(local_user_view: &LocalUserView) -> Result<(), LemmyError> {
85 if !local_user_view.local_user.admin {
86 return Err(ApiError::err("not_an_admin").into());
91 pub(crate) async fn get_post(post_id: i32, pool: &DbPool) -> Result<Post, LemmyError> {
92 match blocking(pool, move |conn| Post::read(conn, post_id)).await? {
94 Err(_e) => Err(ApiError::err("couldnt_find_post").into()),
98 pub(crate) async fn get_local_user_view_from_jwt(
101 ) -> Result<LocalUserView, LemmyError> {
102 let claims = match Claims::decode(&jwt) {
103 Ok(claims) => claims.claims,
104 Err(_e) => return Err(ApiError::err("not_logged_in").into()),
106 let person_id = claims.id;
107 let local_user_view = blocking(pool, move |conn| {
108 LocalUserView::read_person(conn, person_id)
111 // Check for a site ban
112 if local_user_view.person.banned {
113 return Err(ApiError::err("site_ban").into());
118 pub(crate) async fn get_local_user_view_from_jwt_opt(
119 jwt: &Option<String>,
121 ) -> Result<Option<LocalUserView>, LemmyError> {
123 Some(jwt) => Ok(Some(get_local_user_view_from_jwt(jwt, pool).await?)),
128 pub(crate) async fn get_local_user_settings_view_from_jwt(
131 ) -> Result<LocalUserSettingsView, LemmyError> {
132 let claims = match Claims::decode(&jwt) {
133 Ok(claims) => claims.claims,
134 Err(_e) => return Err(ApiError::err("not_logged_in").into()),
136 let person_id = claims.id;
137 let local_user_view = blocking(pool, move |conn| {
138 LocalUserSettingsView::read(conn, person_id)
141 // Check for a site ban
142 if local_user_view.person.banned {
143 return Err(ApiError::err("site_ban").into());
148 pub(crate) async fn get_local_user_settings_view_from_jwt_opt(
149 jwt: &Option<String>,
151 ) -> Result<Option<LocalUserSettingsView>, LemmyError> {
153 Some(jwt) => Ok(Some(
154 get_local_user_settings_view_from_jwt(jwt, pool).await?,
160 pub(crate) async fn check_community_ban(
164 ) -> Result<(), LemmyError> {
166 move |conn: &'_ _| CommunityPersonBanView::get(conn, person_id, community_id).is_ok();
167 if blocking(pool, is_banned).await? {
168 Err(ApiError::err("community_ban").into())
174 pub(crate) async fn check_downvotes_enabled(score: i16, pool: &DbPool) -> Result<(), LemmyError> {
176 let site = blocking(pool, move |conn| Site::read_simple(conn)).await??;
177 if !site.enable_downvotes {
178 return Err(ApiError::err("downvotes_disabled").into());
184 /// Returns a list of communities that the user moderates
185 /// or if a community_id is supplied validates the user is a moderator
186 /// of that community and returns the community id in a vec
188 /// * `user_id` - the user id of the moderator
189 /// * `community_id` - optional community id to check for moderator privileges
190 /// * `pool` - the diesel db pool
191 pub(crate) async fn collect_moderated_communities(
193 community_id: Option<i32>,
195 ) -> Result<Vec<i32>, LemmyError> {
196 if let Some(community_id) = community_id {
197 // if the user provides a community_id, just check for mod/admin privileges
198 is_mod_or_admin(pool, user_id, community_id).await?;
199 Ok(vec![community_id])
201 let ids = blocking(pool, move |conn: &'_ _| {
202 CommunityModerator::get_person_moderated_communities(conn, user_id)
209 pub(crate) async fn build_federated_instances(
211 ) -> Result<Option<FederatedInstances>, LemmyError> {
212 if Settings::get().federation().enabled {
213 let distinct_communities = blocking(pool, move |conn| {
214 Community::distinct_federated_communities(conn)
218 let allowed = Settings::get().get_allowed_instances();
219 let blocked = Settings::get().get_blocked_instances();
221 let mut linked = distinct_communities
223 .map(|actor_id| Ok(Url::parse(actor_id)?.host_str().unwrap_or("").to_string()))
224 .collect::<Result<Vec<String>, LemmyError>>()?;
226 if let Some(allowed) = allowed.as_ref() {
227 linked.extend_from_slice(allowed);
230 if let Some(blocked) = blocked.as_ref() {
231 linked.retain(|a| !blocked.contains(a) && !a.eq(&Settings::get().hostname()));
234 // Sort and remove dupes
235 linked.sort_unstable();
238 Ok(Some(FederatedInstances {
248 pub async fn match_websocket_operation(
249 context: LemmyContext,
253 ) -> Result<String, LemmyError> {
256 UserOperation::Login => do_websocket_operation::<Login>(context, id, op, data).await,
257 UserOperation::Register => do_websocket_operation::<Register>(context, id, op, data).await,
258 UserOperation::GetCaptcha => do_websocket_operation::<GetCaptcha>(context, id, op, data).await,
259 UserOperation::GetPersonDetails => {
260 do_websocket_operation::<GetPersonDetails>(context, id, op, data).await
262 UserOperation::GetReplies => do_websocket_operation::<GetReplies>(context, id, op, data).await,
263 UserOperation::AddAdmin => do_websocket_operation::<AddAdmin>(context, id, op, data).await,
264 UserOperation::BanPerson => do_websocket_operation::<BanPerson>(context, id, op, data).await,
265 UserOperation::GetPersonMentions => {
266 do_websocket_operation::<GetPersonMentions>(context, id, op, data).await
268 UserOperation::MarkPersonMentionAsRead => {
269 do_websocket_operation::<MarkPersonMentionAsRead>(context, id, op, data).await
271 UserOperation::MarkAllAsRead => {
272 do_websocket_operation::<MarkAllAsRead>(context, id, op, data).await
274 UserOperation::DeleteAccount => {
275 do_websocket_operation::<DeleteAccount>(context, id, op, data).await
277 UserOperation::PasswordReset => {
278 do_websocket_operation::<PasswordReset>(context, id, op, data).await
280 UserOperation::PasswordChange => {
281 do_websocket_operation::<PasswordChange>(context, id, op, data).await
283 UserOperation::UserJoin => do_websocket_operation::<UserJoin>(context, id, op, data).await,
284 UserOperation::PostJoin => do_websocket_operation::<PostJoin>(context, id, op, data).await,
285 UserOperation::CommunityJoin => {
286 do_websocket_operation::<CommunityJoin>(context, id, op, data).await
288 UserOperation::ModJoin => do_websocket_operation::<ModJoin>(context, id, op, data).await,
289 UserOperation::SaveUserSettings => {
290 do_websocket_operation::<SaveUserSettings>(context, id, op, data).await
292 UserOperation::GetReportCount => {
293 do_websocket_operation::<GetReportCount>(context, id, op, data).await
296 // Private Message ops
297 UserOperation::CreatePrivateMessage => {
298 do_websocket_operation::<CreatePrivateMessage>(context, id, op, data).await
300 UserOperation::EditPrivateMessage => {
301 do_websocket_operation::<EditPrivateMessage>(context, id, op, data).await
303 UserOperation::DeletePrivateMessage => {
304 do_websocket_operation::<DeletePrivateMessage>(context, id, op, data).await
306 UserOperation::MarkPrivateMessageAsRead => {
307 do_websocket_operation::<MarkPrivateMessageAsRead>(context, id, op, data).await
309 UserOperation::GetPrivateMessages => {
310 do_websocket_operation::<GetPrivateMessages>(context, id, op, data).await
314 UserOperation::GetModlog => do_websocket_operation::<GetModlog>(context, id, op, data).await,
315 UserOperation::CreateSite => do_websocket_operation::<CreateSite>(context, id, op, data).await,
316 UserOperation::EditSite => do_websocket_operation::<EditSite>(context, id, op, data).await,
317 UserOperation::GetSite => do_websocket_operation::<GetSite>(context, id, op, data).await,
318 UserOperation::GetSiteConfig => {
319 do_websocket_operation::<GetSiteConfig>(context, id, op, data).await
321 UserOperation::SaveSiteConfig => {
322 do_websocket_operation::<SaveSiteConfig>(context, id, op, data).await
324 UserOperation::Search => do_websocket_operation::<Search>(context, id, op, data).await,
325 UserOperation::TransferCommunity => {
326 do_websocket_operation::<TransferCommunity>(context, id, op, data).await
328 UserOperation::TransferSite => {
329 do_websocket_operation::<TransferSite>(context, id, op, data).await
333 UserOperation::GetCommunity => {
334 do_websocket_operation::<GetCommunity>(context, id, op, data).await
336 UserOperation::ListCommunities => {
337 do_websocket_operation::<ListCommunities>(context, id, op, data).await
339 UserOperation::CreateCommunity => {
340 do_websocket_operation::<CreateCommunity>(context, id, op, data).await
342 UserOperation::EditCommunity => {
343 do_websocket_operation::<EditCommunity>(context, id, op, data).await
345 UserOperation::DeleteCommunity => {
346 do_websocket_operation::<DeleteCommunity>(context, id, op, data).await
348 UserOperation::RemoveCommunity => {
349 do_websocket_operation::<RemoveCommunity>(context, id, op, data).await
351 UserOperation::FollowCommunity => {
352 do_websocket_operation::<FollowCommunity>(context, id, op, data).await
354 UserOperation::GetFollowedCommunities => {
355 do_websocket_operation::<GetFollowedCommunities>(context, id, op, data).await
357 UserOperation::BanFromCommunity => {
358 do_websocket_operation::<BanFromCommunity>(context, id, op, data).await
360 UserOperation::AddModToCommunity => {
361 do_websocket_operation::<AddModToCommunity>(context, id, op, data).await
365 UserOperation::CreatePost => do_websocket_operation::<CreatePost>(context, id, op, data).await,
366 UserOperation::GetPost => do_websocket_operation::<GetPost>(context, id, op, data).await,
367 UserOperation::GetPosts => do_websocket_operation::<GetPosts>(context, id, op, data).await,
368 UserOperation::EditPost => do_websocket_operation::<EditPost>(context, id, op, data).await,
369 UserOperation::DeletePost => do_websocket_operation::<DeletePost>(context, id, op, data).await,
370 UserOperation::RemovePost => do_websocket_operation::<RemovePost>(context, id, op, data).await,
371 UserOperation::LockPost => do_websocket_operation::<LockPost>(context, id, op, data).await,
372 UserOperation::StickyPost => do_websocket_operation::<StickyPost>(context, id, op, data).await,
373 UserOperation::CreatePostLike => {
374 do_websocket_operation::<CreatePostLike>(context, id, op, data).await
376 UserOperation::SavePost => do_websocket_operation::<SavePost>(context, id, op, data).await,
377 UserOperation::CreatePostReport => {
378 do_websocket_operation::<CreatePostReport>(context, id, op, data).await
380 UserOperation::ListPostReports => {
381 do_websocket_operation::<ListPostReports>(context, id, op, data).await
383 UserOperation::ResolvePostReport => {
384 do_websocket_operation::<ResolvePostReport>(context, id, op, data).await
388 UserOperation::CreateComment => {
389 do_websocket_operation::<CreateComment>(context, id, op, data).await
391 UserOperation::EditComment => {
392 do_websocket_operation::<EditComment>(context, id, op, data).await
394 UserOperation::DeleteComment => {
395 do_websocket_operation::<DeleteComment>(context, id, op, data).await
397 UserOperation::RemoveComment => {
398 do_websocket_operation::<RemoveComment>(context, id, op, data).await
400 UserOperation::MarkCommentAsRead => {
401 do_websocket_operation::<MarkCommentAsRead>(context, id, op, data).await
403 UserOperation::SaveComment => {
404 do_websocket_operation::<SaveComment>(context, id, op, data).await
406 UserOperation::GetComments => {
407 do_websocket_operation::<GetComments>(context, id, op, data).await
409 UserOperation::CreateCommentLike => {
410 do_websocket_operation::<CreateCommentLike>(context, id, op, data).await
412 UserOperation::CreateCommentReport => {
413 do_websocket_operation::<CreateCommentReport>(context, id, op, data).await
415 UserOperation::ListCommentReports => {
416 do_websocket_operation::<ListCommentReports>(context, id, op, data).await
418 UserOperation::ResolveCommentReport => {
419 do_websocket_operation::<ResolveCommentReport>(context, id, op, data).await
424 async fn do_websocket_operation<'a, 'b, Data>(
425 context: LemmyContext,
429 ) -> Result<String, LemmyError>
431 for<'de> Data: Deserialize<'de> + 'a,
434 let parsed_data: Data = serde_json::from_str(&data)?;
435 let res = parsed_data
436 .perform(&web::Data::new(context), Some(id))
438 serialize_websocket_message(&op, &res)
441 pub(crate) fn captcha_espeak_wav_base64(captcha: &str) -> Result<String, LemmyError> {
442 let mut built_text = String::new();
444 // Building proper speech text for espeak
445 for mut c in captcha.chars() {
446 let new_str = if c.is_alphabetic() {
447 if c.is_lowercase() {
448 c.make_ascii_uppercase();
449 format!("lower case {} ... ", c)
451 c.make_ascii_uppercase();
452 format!("capital {} ... ", c)
458 built_text.push_str(&new_str);
461 espeak_wav_base64(&built_text)
464 pub(crate) fn espeak_wav_base64(text: &str) -> Result<String, LemmyError> {
465 // Make a temp file path
466 let uuid = uuid::Uuid::new_v4().to_string();
467 let file_path = format!("/tmp/lemmy_espeak_{}.wav", &uuid);
469 // Write the wav file
470 Command::new("espeak")
476 // Read the wav file bytes
477 let bytes = std::fs::read(&file_path)?;
480 std::fs::remove_file(file_path)?;
483 let base64 = base64::encode(bytes);
488 /// Checks the password length
489 pub(crate) fn password_length_check(pass: &str) -> Result<(), LemmyError> {
491 Err(ApiError::err("invalid_password").into())
499 use crate::captcha_espeak_wav_base64;
503 assert!(captcha_espeak_wav_base64("WxRt2l").is_ok())