]> Untitled Git - lemmy.git/blobdiff - crates/api_common/src/utils.rs
Get rid of Safe Views, use serde_skip (#2767)
[lemmy.git] / crates / api_common / src / utils.rs
index 1e863e855c4ec266ddae4865df5ef640ea7ed545..c4828d352a8dfb40172b2bb47048a293516218f7 100644 (file)
@@ -1,14 +1,15 @@
 use crate::{request::purge_image_from_pictrs, sensitive::Sensitive, site::FederatedInstances};
+use anyhow::Context;
 use chrono::NaiveDateTime;
 use lemmy_db_schema::{
   impls::person::is_banned,
-  newtypes::{CommunityId, LocalUserId, PersonId, PostId},
+  newtypes::{CommunityId, DbUrl, LocalUserId, PersonId, PostId},
   source::{
     comment::{Comment, CommentUpdateForm},
     community::{Community, CommunityUpdateForm},
     email_verification::{EmailVerification, EmailVerificationForm},
     instance::Instance,
-    local_site::LocalSite,
+    local_site::{LocalSite, RegistrationMode},
     local_site_rate_limit::LocalSiteRateLimit,
     password_reset_request::PasswordResetRequest,
     person::{Person, PersonUpdateForm},
@@ -21,28 +22,28 @@ use lemmy_db_schema::{
   utils::DbPool,
   ListingType,
 };
-use lemmy_db_views::{
-  comment_view::CommentQuery,
-  structs::{LocalUserSettingsView, LocalUserView},
-};
+use lemmy_db_views::{comment_view::CommentQuery, structs::LocalUserView};
 use lemmy_db_views_actor::structs::{
   CommunityModeratorView,
   CommunityPersonBanView,
   CommunityView,
+  PersonView,
 };
 use lemmy_utils::{
   claims::Claims,
   email::{send_email, translations::Lang},
   error::LemmyError,
+  location_info,
   rate_limit::RateLimitConfig,
   settings::structs::Settings,
-  utils::{build_slur_regex, generate_random_string},
+  utils::slurs::build_slur_regex,
 };
 use regex::Regex;
 use reqwest_middleware::ClientWithMiddleware;
 use rosetta_i18n::{Language, LanguageId};
 use std::str::FromStr;
 use tracing::warn;
+use url::{ParseError, Url};
 
 #[tracing::instrument(skip_all)]
 pub async fn is_mod_or_admin(
@@ -57,6 +58,35 @@ pub async fn is_mod_or_admin(
   Ok(())
 }
 
+#[tracing::instrument(skip_all)]
+pub async fn is_mod_or_admin_opt(
+  pool: &DbPool,
+  local_user_view: Option<&LocalUserView>,
+  community_id: Option<CommunityId>,
+) -> Result<(), LemmyError> {
+  if let Some(local_user_view) = local_user_view {
+    if let Some(community_id) = community_id {
+      is_mod_or_admin(pool, local_user_view.person.id, community_id).await
+    } else {
+      is_admin(local_user_view)
+    }
+  } else {
+    Err(LemmyError::from_message("not_a_mod_or_admin"))
+  }
+}
+
+pub async fn is_top_admin(pool: &DbPool, person_id: PersonId) -> Result<(), LemmyError> {
+  let admins = PersonView::admins(pool).await?;
+  let top_admin = admins
+    .first()
+    .ok_or_else(|| LemmyError::from_message("no admins"))?;
+
+  if top_admin.person.id != person_id {
+    return Err(LemmyError::from_message("not_top_admin"));
+  }
+  Ok(())
+}
+
 pub fn is_admin(local_user_view: &LocalUserView) -> Result<(), LemmyError> {
   if !local_user_view.person.admin {
     return Err(LemmyError::from_message("not_an_admin"));
@@ -64,6 +94,21 @@ pub fn is_admin(local_user_view: &LocalUserView) -> Result<(), LemmyError> {
   Ok(())
 }
 
+pub fn is_top_mod(
+  local_user_view: &LocalUserView,
+  community_mods: &[CommunityModeratorView],
+) -> Result<(), LemmyError> {
+  if local_user_view.person.id
+    != community_mods
+      .first()
+      .map(|cm| cm.moderator.id)
+      .unwrap_or(PersonId(0))
+  {
+    return Err(LemmyError::from_message("not_top_mod"));
+  }
+  Ok(())
+}
+
 #[tracing::instrument(skip_all)]
 pub async fn get_post(post_id: PostId, pool: &DbPool) -> Result<Post, LemmyError> {
   Post::read(pool, post_id)
@@ -149,14 +194,14 @@ pub async fn get_local_user_settings_view_from_jwt_opt(
   jwt: Option<&Sensitive<String>>,
   pool: &DbPool,
   secret: &Secret,
-) -> Result<Option<LocalUserSettingsView>, LemmyError> {
+) -> Result<Option<LocalUserView>, LemmyError> {
   match jwt {
     Some(jwt) => {
       let claims = Claims::decode(jwt.as_ref(), &secret.jwt_secret)
         .map_err(|e| e.with_message("not_logged_in"))?
         .claims;
       let local_user_id = LocalUserId(claims.sub);
-      let local_user_view = LocalUserSettingsView::read(pool, local_user_id).await?;
+      let local_user_view = LocalUserView::read(pool, local_user_id).await?;
       check_user_valid(
         local_user_view.person.banned,
         local_user_view.person.ban_expires,
@@ -344,7 +389,7 @@ pub async fn send_password_reset_email(
   settings: &Settings,
 ) -> Result<(), LemmyError> {
   // Generate a random token
-  let token = generate_random_string();
+  let token = uuid::Uuid::new_v4().to_string();
 
   // Insert the row
   let token2 = token.clone();
@@ -370,7 +415,7 @@ pub async fn send_verification_email(
   let form = EmailVerificationForm {
     local_user_id: user.local_user.id,
     email: new_email.to_string(),
-    verification_token: generate_random_string(),
+    verification_token: uuid::Uuid::new_v4().to_string(),
   };
   let verify_link = format!(
     "{}/verify_email/{}",
@@ -402,7 +447,7 @@ pub fn get_interface_language(user: &LocalUserView) -> Lang {
   lang_str_to_lang(&user.local_user.interface_language)
 }
 
-pub fn get_interface_language_from_settings(user: &LocalUserSettingsView) -> Lang {
+pub fn get_interface_language_from_settings(user: &LocalUserView) -> Lang {
   lang_str_to_lang(&user.local_user.interface_language)
 }
 
@@ -463,7 +508,7 @@ pub async fn send_new_applicant_email_to_admins(
   settings: &Settings,
 ) -> Result<(), LemmyError> {
   // Collect the admins with emails
-  let admins = LocalUserSettingsView::list_admins_with_emails(pool).await?;
+  let admins = LocalUserView::list_admins_with_emails(pool).await?;
 
   let applications_link = &format!(
     "{}/registration_applications",
@@ -473,19 +518,41 @@ pub async fn send_new_applicant_email_to_admins(
   for admin in &admins {
     let email = &admin.local_user.email.clone().expect("email");
     let lang = get_interface_language_from_settings(admin);
-    let subject = lang.new_application_subject(applicant_username, &settings.hostname);
+    let subject = lang.new_application_subject(&settings.hostname, applicant_username);
     let body = lang.new_application_body(applications_link);
     send_email(&subject, email, &admin.person.name, &body, settings)?;
   }
   Ok(())
 }
 
+/// Send a report to all admins
+pub async fn send_new_report_email_to_admins(
+  reporter_username: &str,
+  reported_username: &str,
+  pool: &DbPool,
+  settings: &Settings,
+) -> Result<(), LemmyError> {
+  // Collect the admins with emails
+  let admins = LocalUserView::list_admins_with_emails(pool).await?;
+
+  let reports_link = &format!("{}/reports", settings.get_protocol_and_hostname(),);
+
+  for admin in &admins {
+    let email = &admin.local_user.email.clone().expect("email");
+    let lang = get_interface_language_from_settings(admin);
+    let subject = lang.new_report_subject(&settings.hostname, reporter_username, reported_username);
+    let body = lang.new_report_body(reports_link);
+    send_email(&subject, email, &admin.person.name, &body, settings)?;
+  }
+  Ok(())
+}
+
 pub async fn check_registration_application(
   local_user_view: &LocalUserView,
   local_site: &LocalSite,
   pool: &DbPool,
 ) -> Result<(), LemmyError> {
-  if local_site.require_application
+  if local_site.registration_mode == RegistrationMode::RequireApplication
     && !local_user_view.local_user.accepted_application
     && !local_user_view.person.admin
   {
@@ -743,3 +810,69 @@ mod tests {
     assert!(honeypot_check(&Some("message".to_string())).is_err());
   }
 }
+
+pub enum EndpointType {
+  Community,
+  Person,
+  Post,
+  Comment,
+  PrivateMessage,
+}
+
+/// Generates an apub endpoint for a given domain, IE xyz.tld
+pub fn generate_local_apub_endpoint(
+  endpoint_type: EndpointType,
+  name: &str,
+  domain: &str,
+) -> Result<DbUrl, ParseError> {
+  let point = match endpoint_type {
+    EndpointType::Community => "c",
+    EndpointType::Person => "u",
+    EndpointType::Post => "post",
+    EndpointType::Comment => "comment",
+    EndpointType::PrivateMessage => "private_message",
+  };
+
+  Ok(Url::parse(&format!("{domain}/{point}/{name}"))?.into())
+}
+
+pub fn generate_followers_url(actor_id: &DbUrl) -> Result<DbUrl, ParseError> {
+  Ok(Url::parse(&format!("{actor_id}/followers"))?.into())
+}
+
+pub fn generate_inbox_url(actor_id: &DbUrl) -> Result<DbUrl, ParseError> {
+  Ok(Url::parse(&format!("{actor_id}/inbox"))?.into())
+}
+
+pub fn generate_site_inbox_url(actor_id: &DbUrl) -> Result<DbUrl, ParseError> {
+  let mut actor_id: Url = actor_id.clone().into();
+  actor_id.set_path("site_inbox");
+  Ok(actor_id.into())
+}
+
+pub fn generate_shared_inbox_url(actor_id: &DbUrl) -> Result<DbUrl, LemmyError> {
+  let actor_id: Url = actor_id.clone().into();
+  let url = format!(
+    "{}://{}{}/inbox",
+    &actor_id.scheme(),
+    &actor_id.host_str().context(location_info!())?,
+    if let Some(port) = actor_id.port() {
+      format!(":{port}")
+    } else {
+      String::new()
+    },
+  );
+  Ok(Url::parse(&url)?.into())
+}
+
+pub fn generate_outbox_url(actor_id: &DbUrl) -> Result<DbUrl, ParseError> {
+  Ok(Url::parse(&format!("{actor_id}/outbox"))?.into())
+}
+
+pub fn generate_featured_url(actor_id: &DbUrl) -> Result<DbUrl, ParseError> {
+  Ok(Url::parse(&format!("{actor_id}/featured"))?.into())
+}
+
+pub fn generate_moderators_url(community_id: &DbUrl) -> Result<DbUrl, LemmyError> {
+  Ok(Url::parse(&format!("{community_id}/moderators"))?.into())
+}