@ -6,14 +6,13 @@ use crate::{
use chrono ::{ DateTime , Days , Local , TimeZone , Utc } ;
use enum_map ::{ enum_map , EnumMap } ;
use lemmy_db_schema ::{
aggregates ::structs ::{ PersonPostAggregates , PersonPostAggregatesForm } ,
newtypes ::{ CommunityId , DbUrl , InstanceId , PersonId , PostId } ,
source ::{
comment ::{ Comment , CommentUpdateForm } ,
community ::{ Community , CommunityModerator , CommunityUpdateForm } ,
community_block ::CommunityBlock ,
email_verification ::{ EmailVerification , EmailVerificationForm } ,
images ::RemoteImage ,
images ::{ LocalImage , RemoteImage } ,
instance ::Instance ,
instance_block ::InstanceBlock ,
local_site ::LocalSite ,
@ -28,10 +27,7 @@ use lemmy_db_schema::{
traits ::Crud ,
utils ::DbPool ,
} ;
use lemmy_db_views ::{
comment_view ::CommentQuery ,
structs ::{ LocalImageView , LocalUserView } ,
} ;
use lemmy_db_views ::{ comment_view ::CommentQuery , structs ::LocalUserView } ;
use lemmy_db_views_actor ::structs ::{
CommunityModeratorView ,
CommunityPersonBanView ,
@ -46,7 +42,7 @@ use lemmy_utils::{
markdown ::{ markdown_check_for_blocked_urls , markdown_rewrite_image_links } ,
slurs ::{ build_slur_regex , remove_slurs } ,
} ,
CACHE_DURATION_ FEDERATION ,
CACHE_DURATION_ SHORT ,
} ;
use moka ::future ::Cache ;
use once_cell ::sync ::Lazy ;
@ -64,7 +60,7 @@ pub async fn is_mod_or_admin(
pool : & mut DbPool < ' _ > ,
person : & Person ,
community_id : CommunityId ,
) -> LemmyResult < ( ) > {
) -> Result < ( ) , LemmyError > {
check_user_valid ( person ) ? ;
let is_mod_or_admin = CommunityView ::is_mod_or_admin ( pool , person . id , community_id ) . await ? ;
@ -80,7 +76,7 @@ pub async fn is_mod_or_admin_opt(
pool : & mut DbPool < ' _ > ,
local_user_view : Option < & LocalUserView > ,
community_id : Option < CommunityId > ,
) -> LemmyResult < ( ) > {
) -> 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 , community_id ) . await
@ -112,7 +108,7 @@ pub async fn check_community_mod_of_any_or_admin_action(
}
}
pub fn is_admin ( local_user_view : & LocalUserView ) -> LemmyResult < ( ) > {
pub fn is_admin ( local_user_view : & LocalUserView ) -> Result < ( ) , LemmyError > {
check_user_valid ( & local_user_view . person ) ? ;
if ! local_user_view . local_user . admin {
Err ( LemmyErrorType ::NotAnAdmin ) ?
@ -126,7 +122,7 @@ pub fn is_admin(local_user_view: &LocalUserView) -> LemmyResult<()> {
pub fn is_top_mod (
local_user_view : & LocalUserView ,
community_mods : & [ CommunityModeratorView ] ,
) -> LemmyResult < ( ) > {
) -> Result < ( ) , LemmyError > {
check_user_valid ( & local_user_view . person ) ? ;
if local_user_view . person . id
! = community_mods
@ -140,42 +136,26 @@ pub fn is_top_mod(
}
}
/// Marks a post as read for a given person.
#[ tracing::instrument(skip_all) ]
pub async fn mark_post_as_read (
person_id : PersonId ,
post_id : PostId ,
pool : & mut DbPool < ' _ > ,
) -> LemmyResult < ( ) > {
PostRead ::mark_as_read ( pool , HashSet ::from ( [ post_id ] ) , person_id )
pub async fn get_post ( post_id : PostId , pool : & mut DbPool < ' _ > ) -> Result < Post , LemmyError > {
Post ::read ( pool , post_id )
. await
. with_lemmy_type ( LemmyErrorType ::CouldntMarkPostAsRead ) ? ;
Ok ( ( ) )
. with_lemmy_type ( LemmyErrorType ::CouldntFindPost )
}
/// Updates the read comment count for a post. Usually done when reading or creating a new comment.
#[ tracing::instrument(skip_all) ]
pub async fn update_read_comments (
pub async fn mark_post_as_read (
person_id : PersonId ,
post_id : PostId ,
read_comments : i64 ,
pool : & mut DbPool < ' _ > ,
) -> LemmyResult < ( ) > {
let person_post_agg_form = PersonPostAggregatesForm {
person_id ,
post_id ,
read_comments ,
.. PersonPostAggregatesForm ::default ( )
} ;
PersonPostAggregates ::upsert ( pool , & person_post_agg_form )
) -> Result < ( ) , LemmyError > {
PostRead ::mark_as_read ( pool , HashSet ::from ( [ post_id ] ) , person_id )
. await
. with_lemmy_type ( LemmyErrorType ::CouldntFindPost ) ? ;
. with_lemmy_type ( LemmyErrorType ::CouldntMarkPostAsRead ) ? ;
Ok ( ( ) )
}
pub fn check_user_valid ( person : & Person ) -> LemmyResult < ( ) > {
pub fn check_user_valid ( person : & Person ) -> Result < ( ) , LemmyError > {
// Check for a site ban
if person . banned {
Err ( LemmyErrorType ::SiteBan ) ?
@ -208,8 +188,8 @@ async fn check_community_deleted_removed(
pool : & mut DbPool < ' _ > ,
) -> LemmyResult < ( ) > {
let community = Community ::read ( pool , community_id )
. await ?
. ok_or ( LemmyErrorType ::CouldntFindCommunity ) ? ;
. await
. with_lemmy_type ( LemmyErrorType ::CouldntFindCommunity ) ? ;
if community . deleted | | community . removed {
Err ( LemmyErrorType ::Deleted ) ?
}
@ -250,7 +230,7 @@ pub async fn check_community_mod_action(
}
/// Don't allow creating reports for removed / deleted posts
pub fn check_post_deleted_or_removed ( post : & Post ) -> LemmyResult < ( ) > {
pub fn check_post_deleted_or_removed ( post : & Post ) -> Result < ( ) , LemmyError > {
if post . deleted | | post . removed {
Err ( LemmyErrorType ::Deleted ) ?
} else {
@ -258,7 +238,7 @@ pub fn check_post_deleted_or_removed(post: &Post) -> LemmyResult<()> {
}
}
pub fn check_comment_deleted_or_removed ( comment : & Comment ) -> LemmyResult < ( ) > {
pub fn check_comment_deleted_or_removed ( comment : & Comment ) -> Result < ( ) , LemmyError > {
if comment . deleted | | comment . removed {
Err ( LemmyErrorType ::Deleted ) ?
} else {
@ -272,7 +252,7 @@ pub async fn check_person_block(
my_id : PersonId ,
potential_blocker_id : PersonId ,
pool : & mut DbPool < ' _ > ,
) -> LemmyResult < ( ) > {
) -> Result < ( ) , LemmyError > {
let is_blocked = PersonBlock ::read ( pool , potential_blocker_id , my_id ) . await ? ;
if is_blocked {
Err ( LemmyErrorType ::PersonIsBlocked ) ?
@ -287,7 +267,7 @@ async fn check_community_block(
community_id : CommunityId ,
person_id : PersonId ,
pool : & mut DbPool < ' _ > ,
) -> LemmyResult < ( ) > {
) -> Result < ( ) , LemmyError > {
let is_blocked = CommunityBlock ::read ( pool , person_id , community_id ) . await ? ;
if is_blocked {
Err ( LemmyErrorType ::CommunityIsBlocked ) ?
@ -302,7 +282,7 @@ async fn check_instance_block(
instance_id : InstanceId ,
person_id : PersonId ,
pool : & mut DbPool < ' _ > ,
) -> LemmyResult < ( ) > {
) -> Result < ( ) , LemmyError > {
let is_blocked = InstanceBlock ::read ( pool , person_id , instance_id ) . await ? ;
if is_blocked {
Err ( LemmyErrorType ::InstanceIsBlocked ) ?
@ -315,18 +295,18 @@ async fn check_instance_block(
pub async fn check_person_instance_community_block (
my_id : PersonId ,
potential_blocker_id : PersonId ,
community_ instance_id: InstanceId ,
instance_id: InstanceId ,
community_id : CommunityId ,
pool : & mut DbPool < ' _ > ,
) -> LemmyResult < ( ) > {
) -> Result < ( ) , LemmyError > {
check_person_block ( my_id , potential_blocker_id , pool ) . await ? ;
check_instance_block ( community_ instance_id, potential_blocker_id , pool ) . await ? ;
check_instance_block ( instance_id, potential_blocker_id , pool ) . await ? ;
check_community_block ( community_id , potential_blocker_id , pool ) . await ? ;
Ok ( ( ) )
}
#[ tracing::instrument(skip_all) ]
pub fn check_downvotes_enabled ( score : i16 , local_site : & LocalSite ) -> LemmyResult < ( ) > {
pub fn check_downvotes_enabled ( score : i16 , local_site : & LocalSite ) -> Result < ( ) , LemmyError > {
if score = = - 1 & & ! local_site . enable_downvotes {
Err ( LemmyErrorType ::DownvotesAreDisabled ) ?
} else {
@ -336,7 +316,7 @@ pub fn check_downvotes_enabled(score: i16, local_site: &LocalSite) -> LemmyResul
/// Dont allow bots to do certain actions, like voting
#[ tracing::instrument(skip_all) ]
pub fn check_bot_account ( person : & Person ) -> LemmyResult < ( ) > {
pub fn check_bot_account ( person : & Person ) -> Result < ( ) , LemmyError > {
if person . bot_account {
Err ( LemmyErrorType ::InvalidBotAction ) ?
} else {
@ -348,7 +328,7 @@ pub fn check_bot_account(person: &Person) -> LemmyResult<()> {
pub fn check_private_instance (
local_user_view : & Option < LocalUserView > ,
local_site : & LocalSite ,
) -> LemmyResult < ( ) > {
) -> Result < ( ) , LemmyError > {
if local_user_view . is_none ( ) & & local_site . private_instance {
Err ( LemmyErrorType ::InstanceIsPrivate ) ?
} else {
@ -360,7 +340,7 @@ pub fn check_private_instance(
pub async fn build_federated_instances (
local_site : & LocalSite ,
pool : & mut DbPool < ' _ > ,
) -> Lemmy Result< Option < FederatedInstances > > {
) -> Result< Option < FederatedInstances > , LemmyError > {
if local_site . federation_enabled {
let mut linked = Vec ::new ( ) ;
let mut allowed = Vec ::new ( ) ;
@ -373,8 +353,7 @@ pub async fn build_federated_instances(
federation_state : federation_state . map ( std ::convert ::Into ::into ) ,
} ;
if is_blocked {
// blocked instances will only have an entry here if they had been federated with in the
// past.
// blocked instances will only have an entry here if they had been federated with in the past.
blocked . push ( i ) ;
} else if is_allowed {
allowed . push ( i . clone ( ) ) ;
@ -396,7 +375,7 @@ pub async fn build_federated_instances(
}
/// Checks the password length
pub fn password_length_check ( pass : & str ) -> LemmyResult < ( ) > {
pub fn password_length_check ( pass : & str ) -> Result < ( ) , LemmyError > {
if ! ( 10 ..= 60 ) . contains ( & pass . chars ( ) . count ( ) ) {
Err ( LemmyErrorType ::InvalidPassword ) ?
} else {
@ -405,7 +384,7 @@ pub fn password_length_check(pass: &str) -> LemmyResult<()> {
}
/// Checks for a honeypot. If this field is filled, fail the rest of the function
pub fn honeypot_check ( honeypot : & Option < String > ) -> LemmyResult < ( ) > {
pub fn honeypot_check ( honeypot : & Option < String > ) -> Result < ( ) , LemmyError > {
if honeypot . is_some ( ) & & honeypot ! = & Some ( String ::new ( ) ) {
Err ( LemmyErrorType ::HoneypotFailed ) ?
} else {
@ -443,7 +422,7 @@ pub async fn send_password_reset_email(
user : & LocalUserView ,
pool : & mut DbPool < ' _ > ,
settings : & Settings ,
) -> LemmyResult < ( ) > {
) -> Result < ( ) , LemmyError > {
// Generate a random token
let token = uuid ::Uuid ::new_v4 ( ) . to_string ( ) ;
@ -458,7 +437,7 @@ pub async fn send_password_reset_email(
// Insert the row after successful send, to avoid using daily reset limit while
// email sending is broken.
let local_user_id = user . local_user . id ;
PasswordResetRequest ::create ( pool , local_user_id , token . clone ( ) ) . await ? ;
PasswordResetRequest ::create _token ( pool , local_user_id , token . clone ( ) ) . await ? ;
Ok ( ( ) )
}
@ -468,7 +447,7 @@ pub async fn send_verification_email(
new_email : & str ,
pool : & mut DbPool < ' _ > ,
settings : & Settings ,
) -> LemmyResult < ( ) > {
) -> Result < ( ) , LemmyError > {
let form = EmailVerificationForm {
local_user_id : user . local_user . id ,
email : new_email . to_string ( ) ,
@ -545,7 +524,7 @@ pub async fn get_url_blocklist(context: &LemmyContext) -> LemmyResult<RegexSet>
static URL_BLOCKLIST : Lazy < Cache < ( ) , RegexSet > > = Lazy ::new ( | | {
Cache ::builder ( )
. max_capacity ( 1 )
. time_to_live ( CACHE_DURATION_ FEDERATION )
. time_to_live ( CACHE_DURATION_ SHORT )
. build ( )
} ) ;
@ -554,8 +533,25 @@ pub async fn get_url_blocklist(context: &LemmyContext) -> LemmyResult<RegexSet>
. try_get_with ::< _ , LemmyError > ( ( ) , async {
let urls = LocalSiteUrlBlocklist ::get_all ( & mut context . pool ( ) ) . await ? ;
// The urls are already validated on saving, so just escape them.
let regexes = urls . iter ( ) . map ( | url | escape ( & url . url ) ) ;
let regexes = urls . iter ( ) . map ( | url | {
let url = & url . url ;
let parsed = Url ::parse ( url ) . expect ( "Coundln't parse URL." ) ;
if url . ends_with ( '/' ) {
format! (
"({}://)?{}{}?" ,
parsed . scheme ( ) ,
escape ( parsed . domain ( ) . expect ( "No domain." ) ) ,
escape ( parsed . path ( ) )
)
} else {
format! (
"({}://)?{}{}" ,
parsed . scheme ( ) ,
escape ( parsed . domain ( ) . expect ( "No domain." ) ) ,
escape ( parsed . path ( ) )
)
}
} ) ;
let set = RegexSet ::new ( regexes ) ? ;
Ok ( set )
@ -568,7 +564,7 @@ pub async fn get_url_blocklist(context: &LemmyContext) -> LemmyResult<RegexSet>
pub async fn send_application_approved_email (
user : & LocalUserView ,
settings : & Settings ,
) -> LemmyResult < ( ) > {
) -> Result < ( ) , LemmyError > {
let email = & user . local_user . email . clone ( ) . expect ( "email" ) ;
let lang = get_interface_language ( user ) ;
let subject = lang . registration_approved_subject ( & user . person . actor_id ) ;
@ -581,7 +577,7 @@ pub async fn send_new_applicant_email_to_admins(
applicant_username : & str ,
pool : & mut DbPool < ' _ > ,
settings : & Settings ,
) -> LemmyResult < ( ) > {
) -> Result < ( ) , LemmyError > {
// Collect the admins with emails
let admins = LocalUserView ::list_admins_with_emails ( pool ) . await ? ;
@ -606,7 +602,7 @@ pub async fn send_new_report_email_to_admins(
reported_username : & str ,
pool : & mut DbPool < ' _ > ,
settings : & Settings ,
) -> LemmyResult < ( ) > {
) -> Result < ( ) , LemmyError > {
// Collect the admins with emails
let admins = LocalUserView ::list_admins_with_emails ( pool ) . await ? ;
@ -622,7 +618,9 @@ pub async fn send_new_report_email_to_admins(
Ok ( ( ) )
}
pub fn check_private_instance_and_federation_enabled ( local_site : & LocalSite ) -> LemmyResult < ( ) > {
pub fn check_private_instance_and_federation_enabled (
local_site : & LocalSite ,
) -> Result < ( ) , LemmyError > {
if local_site . private_instance & & local_site . federation_enabled {
Err ( LemmyErrorType ::CantEnablePrivateInstanceAndFederationTogether ) ?
} else {
@ -636,7 +634,7 @@ pub fn check_private_instance_and_federation_enabled(local_site: &LocalSite) ->
pub async fn read_site_for_actor (
actor_id : DbUrl ,
context : & LemmyContext ,
) -> Lemmy Result< Option < Site > > {
) -> Result< Option < Site > , LemmyError > {
let site_id = Site ::instance_actor_id_from_url ( actor_id . clone ( ) . into ( ) ) ;
let site = Site ::read_from_apub_id ( & mut context . pool ( ) , & site_id . into ( ) ) . await ? ;
Ok ( site )
@ -645,7 +643,7 @@ pub async fn read_site_for_actor(
pub async fn purge_image_posts_for_person (
banned_person_id : PersonId ,
context : & LemmyContext ,
) -> LemmyResult < ( ) > {
) -> Result < ( ) , LemmyError > {
let pool = & mut context . pool ( ) ;
let posts = Post ::fetch_pictrs_posts_for_creator ( pool , banned_person_id ) . await ? ;
for post in posts {
@ -663,21 +661,19 @@ pub async fn purge_image_posts_for_person(
}
/// Delete a local_user's images
async fn delete_local_user_images ( person_id : PersonId , context : & LemmyContext ) -> LemmyResult < ( ) > {
if let Ok ( Some ( local_user ) ) = LocalUserView ::read_person ( & mut context . pool ( ) , person_id ) . await {
async fn delete_local_user_images (
person_id : PersonId ,
context : & LemmyContext ,
) -> Result < ( ) , LemmyError > {
if let Ok ( local_user ) = LocalUserView ::read_person ( & mut context . pool ( ) , person_id ) . await {
let pictrs_uploads =
LocalImageView ::get_all_by_local_user_id ( & mut context . pool ( ) , local_user . local_user . id )
. await ? ;
LocalImage ::get_all_by_local_user_id ( & mut context . pool ( ) , local_user . local_user . id ) . await ? ;
// Delete their images
for upload in pictrs_uploads {
delete_image_from_pictrs (
& upload . local_image . pictrs_alias ,
& upload . local_image . pictrs_delete_token ,
context ,
)
. await
. ok ( ) ;
delete_image_from_pictrs ( & upload . pictrs_alias , & upload . pictrs_delete_token , context )
. await
. ok ( ) ;
}
}
Ok ( ( ) )
@ -686,7 +682,7 @@ async fn delete_local_user_images(person_id: PersonId, context: &LemmyContext) -
pub async fn purge_image_posts_for_community (
banned_community_id : CommunityId ,
context : & LemmyContext ,
) -> LemmyResult < ( ) > {
) -> Result < ( ) , LemmyError > {
let pool = & mut context . pool ( ) ;
let posts = Post ::fetch_pictrs_posts_for_community ( pool , banned_community_id ) . await ? ;
for post in posts {
@ -706,12 +702,10 @@ pub async fn purge_image_posts_for_community(
pub async fn remove_user_data (
banned_person_id : PersonId ,
context : & LemmyContext ,
) -> LemmyResult < ( ) > {
) -> Result < ( ) , LemmyError > {
let pool = & mut context . pool ( ) ;
// Purge user images
let person = Person ::read ( pool , banned_person_id )
. await ?
. ok_or ( LemmyErrorType ::CouldntFindPerson ) ? ;
let person = Person ::read ( pool , banned_person_id ) . await ? ;
if let Some ( avatar ) = person . avatar {
purge_image_from_pictrs ( & avatar , context ) . await . ok ( ) ;
}
@ -791,7 +785,7 @@ pub async fn remove_user_data_in_community(
community_id : CommunityId ,
banned_person_id : PersonId ,
pool : & mut DbPool < ' _ > ,
) -> LemmyResult < ( ) > {
) -> Result < ( ) , LemmyError > {
// Posts
Post ::update_removed_for_creator ( pool , banned_person_id , Some ( community_id ) , true ) . await ? ;
@ -821,12 +815,13 @@ pub async fn remove_user_data_in_community(
Ok ( ( ) )
}
pub async fn purge_user_account ( person_id : PersonId , context : & LemmyContext ) -> LemmyResult < ( ) > {
pub async fn purge_user_account (
person_id : PersonId ,
context : & LemmyContext ,
) -> Result < ( ) , LemmyError > {
let pool = & mut context . pool ( ) ;
let person = Person ::read ( pool , person_id )
. await ?
. ok_or ( LemmyErrorType ::CouldntFindPerson ) ? ;
let person = Person ::read ( pool , person_id ) . await ? ;
// Delete their local images, if they're a local user
delete_local_user_images ( person_id , context ) . await . ok ( ) ;
@ -893,7 +888,7 @@ pub fn generate_inbox_url(actor_id: &DbUrl) -> Result<DbUrl, ParseError> {
Ok ( Url ::parse ( & format! ( "{actor_id}/inbox" ) ) ? . into ( ) )
}
pub fn generate_shared_inbox_url ( settings : & Settings ) -> LemmyResult < DbUrl > {
pub fn generate_shared_inbox_url ( settings : & Settings ) -> Result < DbUrl , LemmyError > {
let url = format! ( "{}/inbox" , settings . get_protocol_and_hostname ( ) ) ;
Ok ( Url ::parse ( & url ) ? . into ( ) )
}
@ -906,7 +901,7 @@ 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 ) -> LemmyResult < DbUrl > {
pub fn generate_moderators_url ( community_id : & DbUrl ) -> Result < DbUrl , LemmyError > {
Ok ( Url ::parse ( & format! ( "{community_id}/moderators" ) ) ? . into ( ) )
}
@ -972,8 +967,8 @@ pub async fn process_markdown_opt(
/// A wrapper for `proxy_image_link` for use in tests.
///
/// The parameter `force_image_proxy` is the config value of `pictrs.image_proxy`. Its necessary to
/// pass as separate parameter so it can be changed in tests.
/// The parameter `force_image_proxy` is the config value of `pictrs.image_proxy`. Its necessary to pass
/// as separate parameter so it can be changed in tests.
async fn proxy_image_link_internal (
link : Url ,
image_mode : PictrsImageMode ,
@ -983,10 +978,13 @@ async fn proxy_image_link_internal(
if link . domain ( ) = = Some ( & context . settings ( ) . hostname ) {
Ok ( link . into ( ) )
} else if image_mode = = PictrsImageMode ::ProxyAllImages {
let proxied = build_proxied_image_url ( & link , & context . settings ( ) . get_protocol_and_hostname ( ) ) ? ;
let proxied = format! (
"{}/api/v3/image_proxy?url={}" ,
context . settings ( ) . get_protocol_and_hostname ( ) ,
encode ( link . as_str ( ) )
) ;
RemoteImage ::create ( & mut context . pool ( ) , vec! [ link ] ) . await ? ;
Ok ( proxied . into ( ) )
Ok ( Url::parse ( & proxied) ? . into ( ) )
} else {
Ok ( link . into ( ) )
}
@ -1040,17 +1038,6 @@ pub async fn proxy_image_link_opt_apub(
}
}
fn build_proxied_image_url (
link : & Url ,
protocol_and_hostname : & str ,
) -> Result < Url , url ::ParseError > {
Url ::parse ( & format! (
"{}/api/v3/image_proxy?url={}" ,
protocol_and_hostname ,
encode ( link . as_str ( ) )
) )
}
#[ cfg(test) ]
#[ allow(clippy::unwrap_used) ]
#[ allow(clippy::indexing_slicing) ]