@ -1,4 +1,7 @@
use crate ::{ site ::check_application_question , PerformCrud } ;
use crate ::{
site ::{ application_question_check , site_default_post_listing_type_check } ,
PerformCrud ,
} ;
use activitypub_federation ::http_signatures ::generate_actor_keypair ;
use actix_web ::web ::Data ;
use lemmy_api_common ::{
@ -8,9 +11,7 @@ use lemmy_api_common::{
generate_site_inbox_url ,
is_admin ,
local_site_rate_limit_to_rate_limit_config ,
local_site_to_slur_regex ,
local_user_view_from_jwt ,
site_description_length_check ,
} ,
} ;
use lemmy_db_schema ::{
@ -26,10 +27,16 @@ use lemmy_db_schema::{
} ;
use lemmy_db_views ::structs ::SiteView ;
use lemmy_utils ::{
error ::LemmyError ,
error ::{ LemmyError , LemmyResult } ,
utils ::{
slurs ::{ check_slurs , check_slurs_opt } ,
validation ::{ check_site_visibility_valid , is_valid_body_field } ,
validation ::{
build_and_check_regex ,
check_site_visibility_valid ,
is_valid_body_field ,
site_description_length_check ,
site_name_length_check ,
} ,
} ,
} ;
use url ::Url ;
@ -41,57 +48,23 @@ impl PerformCrud for CreateSite {
#[ tracing::instrument(skip(context)) ]
async fn perform ( & self , context : & Data < LemmyContext > ) -> Result < SiteResponse , LemmyError > {
let data : & CreateSite = self ;
let local_site = LocalSite ::read ( context . pool ( ) ) . await ? ;
if local_site . site_setup {
return Err ( LemmyError ::from_message ( "site_already_exists" ) ) ;
} ;
let local_user_view = local_user_view_from_jwt ( & data . auth , context ) . await ? ;
let local_site = LocalSite ::read ( context . pool ( ) ) . await ? ;
// Make sure user is an admin
// Make sure user is an admin; other types of users should not create site data...
is_admin ( & local_user_view ) ? ;
check_site_visibility_valid (
local_site . private_instance ,
local_site . federation_enabled ,
& data . private_instance ,
& data . federation_enabled ,
) ? ;
let sidebar = diesel_option_overwrite ( & data . sidebar ) ;
let description = diesel_option_overwrite ( & data . description ) ;
let icon = diesel_option_overwrite_to_url ( & data . icon ) ? ;
let banner = diesel_option_overwrite_to_url ( & data . banner ) ? ;
let slur_regex = local_site_to_slur_regex ( & local_site ) ;
check_slurs ( & data . name , & slur_regex ) ? ;
check_slurs_opt ( & data . description , & slur_regex ) ? ;
if let Some ( Some ( desc ) ) = & description {
site_description_length_check ( desc ) ? ;
}
is_valid_body_field ( & data . sidebar , false ) ? ;
let application_question = diesel_option_overwrite ( & data . application_question ) ;
check_application_question (
& application_question ,
data
. registration_mode
. unwrap_or ( local_site . registration_mode ) ,
) ? ;
validate_create_payload ( & local_site , data ) ? ;
let actor_id : DbUrl = Url ::parse ( & context . settings ( ) . get_protocol_and_hostname ( ) ) ? . into ( ) ;
let inbox_url = Some ( generate_site_inbox_url ( & actor_id ) ? ) ;
let keypair = generate_actor_keypair ( ) ? ;
let site_form = SiteUpdateForm ::builder ( )
. name ( Some ( data . name . clone ( ) ) )
. sidebar ( sidebar)
. description ( d escription)
. icon ( icon)
. banner ( banner)
. sidebar ( diesel_option_overwrite ( & data . sidebar ) )
. description ( diesel_option_overwrite ( & data . description ) )
. icon ( diesel_option_overwrite_to_url ( & data . icon ) ? )
. banner ( diesel_option_overwrite_to_url ( & data . banner ) ? )
. actor_id ( Some ( actor_id ) )
. last_refreshed_at ( Some ( naive_now ( ) ) )
. inbox_url ( inbox_url )
@ -111,7 +84,7 @@ impl PerformCrud for CreateSite {
. enable_nsfw ( data . enable_nsfw )
. community_creation_admin_only ( data . community_creation_admin_only )
. require_email_verification ( data . require_email_verification )
. application_question ( application_question)
. application_question ( diesel_option_overwrite( & data . application_question) )
. private_instance ( data . private_instance )
. default_theme ( data . default_theme . clone ( ) )
. default_post_listing_type ( data . default_post_listing_type )
@ -163,3 +136,449 @@ impl PerformCrud for CreateSite {
} )
}
}
fn validate_create_payload ( local_site : & LocalSite , create_site : & CreateSite ) -> LemmyResult < ( ) > {
// Make sure the site hasn't already been set up...
if local_site . site_setup {
return Err ( LemmyError ::from_message ( "site_already_exists" ) ) ;
} ;
// Check that the slur regex compiles, and returns the regex if valid...
// Prioritize using new slur regex from the request; if not provided, use the existing regex.
let slur_regex = build_and_check_regex (
& create_site
. slur_filter_regex
. as_deref ( )
. or ( local_site . slur_filter_regex . as_deref ( ) ) ,
) ? ;
site_name_length_check ( & create_site . name ) ? ;
check_slurs ( & create_site . name , & slur_regex ) ? ;
if let Some ( desc ) = & create_site . description {
site_description_length_check ( desc ) ? ;
check_slurs_opt ( & create_site . description , & slur_regex ) ? ;
}
site_default_post_listing_type_check ( & create_site . default_post_listing_type ) ? ;
check_site_visibility_valid (
local_site . private_instance ,
local_site . federation_enabled ,
& create_site . private_instance ,
& create_site . federation_enabled ,
) ? ;
// Ensure that the sidebar has fewer than the max num characters...
is_valid_body_field ( & create_site . sidebar , false ) ? ;
application_question_check (
& local_site . application_question ,
& create_site . application_question ,
create_site
. registration_mode
. unwrap_or ( local_site . registration_mode ) ,
)
}
#[ cfg(test) ]
mod tests {
use crate ::site ::create ::validate_create_payload ;
use lemmy_api_common ::site ::CreateSite ;
use lemmy_db_schema ::{ source ::local_site ::LocalSite , ListingType , RegistrationMode } ;
#[ test ]
fn test_validate_invalid_create_payload ( ) {
let invalid_payloads = [
(
"CreateSite attempted on set up LocalSite" ,
"site_already_exists" ,
& generate_local_site (
true ,
None ::< String > ,
true ,
false ,
None ::< String > ,
RegistrationMode ::Open ,
) ,
& generate_create_site (
String ::from ( "site_name" ) ,
None ::< String > ,
None ::< String > ,
None ::< ListingType > ,
None ::< String > ,
None ::< bool > ,
None ::< bool > ,
None ::< String > ,
None ::< RegistrationMode > ,
) ,
) ,
(
"CreateSite name matches LocalSite slur filter" ,
"slurs" ,
& generate_local_site (
false ,
Some ( String ::from ( "(foo|bar)" ) ) ,
true ,
false ,
None ::< String > ,
RegistrationMode ::Open ,
) ,
& generate_create_site (
String ::from ( "foo site_name" ) ,
None ::< String > ,
None ::< String > ,
None ::< ListingType > ,
None ::< String > ,
None ::< bool > ,
None ::< bool > ,
None ::< String > ,
None ::< RegistrationMode > ,
) ,
) ,
(
"CreateSite name matches new slur filter" ,
"slurs" ,
& generate_local_site (
false ,
Some ( String ::from ( "(foo|bar)" ) ) ,
true ,
false ,
None ::< String > ,
RegistrationMode ::Open ,
) ,
& generate_create_site (
String ::from ( "zeta site_name" ) ,
None ::< String > ,
None ::< String > ,
None ::< ListingType > ,
Some ( String ::from ( "(zeta|alpha)" ) ) ,
None ::< bool > ,
None ::< bool > ,
None ::< String > ,
None ::< RegistrationMode > ,
) ,
) ,
(
"CreateSite listing type is Subscribed, which is invalid" ,
"invalid_default_post_listing_type" ,
& generate_local_site (
false ,
None ::< String > ,
true ,
false ,
None ::< String > ,
RegistrationMode ::Open ,
) ,
& generate_create_site (
String ::from ( "site_name" ) ,
None ::< String > ,
None ::< String > ,
Some ( ListingType ::Subscribed ) ,
None ::< String > ,
None ::< bool > ,
None ::< bool > ,
None ::< String > ,
None ::< RegistrationMode > ,
) ,
) ,
(
"CreateSite is both private and federated" ,
"cant_enable_private_instance_and_federation_together" ,
& generate_local_site (
false ,
None ::< String > ,
true ,
false ,
None ::< String > ,
RegistrationMode ::Open ,
) ,
& generate_create_site (
String ::from ( "site_name" ) ,
None ::< String > ,
None ::< String > ,
None ::< ListingType > ,
None ::< String > ,
Some ( true ) ,
Some ( true ) ,
None ::< String > ,
None ::< RegistrationMode > ,
) ,
) ,
(
"LocalSite is private, but CreateSite also makes it federated" ,
"cant_enable_private_instance_and_federation_together" ,
& generate_local_site (
false ,
None ::< String > ,
true ,
false ,
None ::< String > ,
RegistrationMode ::Open ,
) ,
& generate_create_site (
String ::from ( "site_name" ) ,
None ::< String > ,
None ::< String > ,
None ::< ListingType > ,
None ::< String > ,
None ::< bool > ,
Some ( true ) ,
None ::< String > ,
None ::< RegistrationMode > ,
) ,
) ,
(
"CreateSite requires application, but neither it nor LocalSite has an application question" ,
"application_question_required" ,
& generate_local_site (
false ,
None ::< String > ,
true ,
false ,
None ::< String > ,
RegistrationMode ::Open ,
) ,
& generate_create_site (
String ::from ( "site_name" ) ,
None ::< String > ,
None ::< String > ,
None ::< ListingType > ,
None ::< String > ,
None ::< bool > ,
None ::< bool > ,
None ::< String > ,
Some ( RegistrationMode ::RequireApplication ) ,
) ,
) ,
] ;
invalid_payloads . iter ( ) . enumerate ( ) . for_each (
| (
idx ,
& ( reason , expected_err , local_site , create_site ) ,
) | {
match validate_create_payload (
local_site ,
create_site ,
) {
Ok ( _ ) = > {
panic! (
"Got Ok, but validation should have failed with error: {} for reason: {}. invalid_payloads.nth({})" ,
expected_err , reason , idx
)
}
Err ( error ) = > {
assert! (
error . message . eq ( & Some ( String ::from ( expected_err ) ) ) ,
"Got Err {:?}, but should have failed with message: {} for reason: {}. invalid_payloads.nth({})" ,
error . message ,
expected_err ,
reason ,
idx
)
}
}
} ,
) ;
}
#[ test ]
fn test_validate_valid_create_payload ( ) {
let valid_payloads = [
(
"No changes between LocalSite and CreateSite" ,
& generate_local_site (
false ,
None ::< String > ,
true ,
false ,
None ::< String > ,
RegistrationMode ::Open ,
) ,
& generate_create_site (
String ::from ( "site_name" ) ,
None ::< String > ,
None ::< String > ,
None ::< ListingType > ,
None ::< String > ,
None ::< bool > ,
None ::< bool > ,
None ::< String > ,
None ::< RegistrationMode > ,
) ,
) ,
(
"CreateSite allows clearing and changing values" ,
& generate_local_site (
false ,
None ::< String > ,
true ,
false ,
None ::< String > ,
RegistrationMode ::Open ,
) ,
& generate_create_site (
String ::from ( "site_name" ) ,
Some ( String ::new ( ) ) ,
Some ( String ::new ( ) ) ,
Some ( ListingType ::All ) ,
Some ( String ::new ( ) ) ,
Some ( false ) ,
Some ( true ) ,
Some ( String ::new ( ) ) ,
Some ( RegistrationMode ::Open ) ,
) ,
) ,
(
"CreateSite clears existing slur filter regex" ,
& generate_local_site (
false ,
Some ( String ::from ( "(foo|bar)" ) ) ,
true ,
false ,
None ::< String > ,
RegistrationMode ::Open ,
) ,
& generate_create_site (
String ::from ( "foo site_name" ) ,
None ::< String > ,
None ::< String > ,
None ::< ListingType > ,
Some ( String ::new ( ) ) ,
None ::< bool > ,
None ::< bool > ,
None ::< String > ,
None ::< RegistrationMode > ,
) ,
) ,
(
"LocalSite has application question and CreateSite now requires applications," ,
& generate_local_site (
false ,
None ::< String > ,
true ,
false ,
Some ( String ::from ( "question" ) ) ,
RegistrationMode ::Open ,
) ,
& generate_create_site (
String ::from ( "site_name" ) ,
None ::< String > ,
None ::< String > ,
None ::< ListingType > ,
None ::< String > ,
None ::< bool > ,
None ::< bool > ,
None ::< String > ,
Some ( RegistrationMode ::RequireApplication ) ,
) ,
) ,
] ;
valid_payloads
. iter ( )
. enumerate ( )
. for_each ( | ( idx , & ( reason , local_site , edit_site ) ) | {
assert! (
validate_create_payload ( local_site , edit_site ) . is_ok ( ) ,
"Got Err, but should have got Ok for reason: {}. valid_payloads.nth({})" ,
reason ,
idx
) ;
} )
}
fn generate_local_site (
site_setup : bool ,
site_slur_filter_regex : Option < String > ,
site_is_private : bool ,
site_is_federated : bool ,
site_application_question : Option < String > ,
site_registration_mode : RegistrationMode ,
) -> LocalSite {
LocalSite {
id : Default ::default ( ) ,
site_id : Default ::default ( ) ,
site_setup ,
enable_downvotes : false ,
enable_nsfw : false ,
community_creation_admin_only : false ,
require_email_verification : false ,
application_question : site_application_question ,
private_instance : site_is_private ,
default_theme : String ::new ( ) ,
default_post_listing_type : ListingType ::All ,
legal_information : None ,
hide_modlog_mod_names : false ,
application_email_admins : false ,
slur_filter_regex : site_slur_filter_regex ,
actor_name_max_length : 0 ,
federation_enabled : site_is_federated ,
captcha_enabled : false ,
captcha_difficulty : String ::new ( ) ,
published : Default ::default ( ) ,
updated : None ,
registration_mode : site_registration_mode ,
reports_email_admins : false ,
}
}
// Allow the test helper function to have too many arguments.
// It's either this or generate the entire struct each time for testing.
#[ allow(clippy::too_many_arguments) ]
fn generate_create_site (
site_name : String ,
site_description : Option < String > ,
site_sidebar : Option < String > ,
site_listing_type : Option < ListingType > ,
site_slur_filter_regex : Option < String > ,
site_is_private : Option < bool > ,
site_is_federated : Option < bool > ,
site_application_question : Option < String > ,
site_registration_mode : Option < RegistrationMode > ,
) -> CreateSite {
CreateSite {
name : site_name ,
sidebar : site_sidebar ,
description : site_description ,
icon : None ,
banner : None ,
enable_downvotes : None ,
enable_nsfw : None ,
community_creation_admin_only : None ,
require_email_verification : None ,
application_question : site_application_question ,
private_instance : site_is_private ,
default_theme : None ,
default_post_listing_type : site_listing_type ,
legal_information : None ,
application_email_admins : None ,
hide_modlog_mod_names : None ,
discussion_languages : None ,
slur_filter_regex : site_slur_filter_regex ,
actor_name_max_length : None ,
rate_limit_message : None ,
rate_limit_message_per_second : None ,
rate_limit_post : None ,
rate_limit_post_per_second : None ,
rate_limit_register : None ,
rate_limit_register_per_second : None ,
rate_limit_image : None ,
rate_limit_image_per_second : None ,
rate_limit_comment : None ,
rate_limit_comment_per_second : None ,
rate_limit_search : None ,
rate_limit_search_per_second : None ,
federation_enabled : site_is_federated ,
federation_debug : None ,
captcha_enabled : None ,
captcha_difficulty : None ,
allowed_instances : None ,
blocked_instances : None ,
taglines : None ,
registration_mode : site_registration_mode ,
auth : Default ::default ( ) ,
}
}
}