diff --git a/Cargo.lock b/Cargo.lock index 70344198af..bc0710b34b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5136,6 +5136,7 @@ name = "primitives" version = "0.0.1" dependencies = [ "my-workspace-hack", + "testing", ] [[package]] diff --git a/crates/lib/data/primitives/Cargo.toml b/crates/lib/data/primitives/Cargo.toml index f09bbcac78..97a0e27a00 100644 --- a/crates/lib/data/primitives/Cargo.toml +++ b/crates/lib/data/primitives/Cargo.toml @@ -17,6 +17,9 @@ path = "src/lib.rs" normal = ["my-workspace-hack"] [dependencies] +# Workspace hack (faster compile times) +my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" } + # NB: This is designed to be a central crate with few dependencies. # Consider the dependencies here wisely. # This is used in MySQL, etc., so dependencies are ill-advised. @@ -27,8 +30,5 @@ normal = ["my-workspace-hack"] # External # (None) -# Workspace hack (faster compile times) -my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" } - [dev-dependencies] -# None yet +testing = { path = "../../testing" } diff --git a/crates/lib/data/primitives/src/iterators/iterate_trimmed_lines_without_comments.rs b/crates/lib/data/primitives/src/iterators/iterate_trimmed_lines_without_comments.rs new file mode 100644 index 0000000000..1dc928f7e4 --- /dev/null +++ b/crates/lib/data/primitives/src/iterators/iterate_trimmed_lines_without_comments.rs @@ -0,0 +1,52 @@ + + +/// Read lines from a file in an iterator. +/// Remove comment lines prefixed with `#`. +pub fn iterate_trimmed_lines_without_comments<'a>(iterator: impl Iterator) + -> impl Iterator +//-> impl Iterator +{ + let f = iterator + .into_iter() + .map(|line| line.trim().to_string()) + .collect::>(); + + let f = f.into_iter() + .filter(|line| !(line.starts_with("#") || line.is_empty())) + .collect::>(); + + f.into_iter() +} + +#[cfg(test)] +mod tests { + use crate::iterators::iterate_trimmed_lines_without_comments::iterate_trimmed_lines_without_comments; + use std::fs::File; + use std::io::Read; + use testing::test_file_path::test_file_path; + + #[test] + fn load_file() { + let filename = test_file_path("test_data/text_files/cidr_ban_example/local_cidrs_example.txt") + .expect("should be a valid path"); + + let mut file = File::open(filename) + .expect("file should open"); + + let mut contents = String::new(); + file.read_to_string(&mut contents) + .expect("should be able to read file"); + + let lines = contents.lines(); + let lines = iterate_trimmed_lines_without_comments(lines); + + let lines: Vec = lines.collect(); + + assert_eq!( + lines, + vec![ + "127.0.0.0/24".to_string(), + "192.168.0.0/24".to_string(), + ]); + } +} diff --git a/crates/lib/data/primitives/src/iterators/mod.rs b/crates/lib/data/primitives/src/iterators/mod.rs new file mode 100644 index 0000000000..3261fee9bb --- /dev/null +++ b/crates/lib/data/primitives/src/iterators/mod.rs @@ -0,0 +1 @@ +pub mod iterate_trimmed_lines_without_comments; \ No newline at end of file diff --git a/crates/lib/data/primitives/src/lib.rs b/crates/lib/data/primitives/src/lib.rs index 969bda7342..1dd6495a04 100644 --- a/crates/lib/data/primitives/src/lib.rs +++ b/crates/lib/data/primitives/src/lib.rs @@ -19,6 +19,7 @@ #![allow(non_snake_case)] pub mod bool_to_str; +pub mod iterators; pub mod lazy_any_option_true; pub mod numerics; pub mod optional_false_to_none; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/list/list_featured_media_files_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/list/list_featured_media_files_handler.rs index 53d46c3ae4..024a736268 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/list/list_featured_media_files_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/list/list_featured_media_files_handler.rs @@ -1,13 +1,13 @@ -use std::sync::Arc; -use std::time::Instant; -use actix_web::{HttpRequest, HttpResponse, web}; use actix_web::error::ResponseError; use actix_web::http::StatusCode; use actix_web::web::Query; +use actix_web::{web, HttpRequest, HttpResponse}; use actix_web_lab::__reexports::tracing::info; use chrono::{DateTime, Utc}; use log::{debug, error, warn}; use r2d2_redis::redis::Commands; +use std::sync::Arc; +use std::time::Instant; use time::ext::InstantExt; use utoipa::{IntoParams, ToSchema}; @@ -23,7 +23,7 @@ use enums::no_table::style_transfer::style_transfer_name::StyleTransferName; use enums_public::by_table::media_files::public_media_file_model_type::PublicMediaFileModelType; use http_server_common::request::get_request_header_optional::get_request_header_optional; use mysql_queries::queries::media_files::get::batch_get_media_files_by_tokens::batch_get_media_files_by_tokens; -use mysql_queries::queries::media_files::list::list_featured_media_files::{FeaturedMediaFileListPage, list_featured_media_files, ListFeaturedMediaFilesArgs}; +use mysql_queries::queries::media_files::list::list_featured_media_files::{list_featured_media_files, FeaturedMediaFileListPage, ListFeaturedMediaFilesArgs}; use tokens::tokens::media_files::MediaFileToken; use crate::http_server::common_responses::media_file_cover_image_details::{MediaFileCoverImageDetails, MediaFileDefaultCover}; @@ -37,14 +37,12 @@ use crate::http_server::endpoints::media_files::helpers::get_scoped_engine_categ use crate::http_server::endpoints::media_files::helpers::get_scoped_media_classes::get_scoped_media_classes; use crate::http_server::endpoints::media_files::helpers::get_scoped_media_types::get_scoped_media_types; use crate::http_server::endpoints::media_files::helpers::get_scoped_product_categories::get_scoped_product_categories; +use crate::http_server::headers::get_cloudflare_ray_header::get_cloudflare_ray_header; use crate::http_server::headers::has_debug_header::has_debug_header; use crate::http_server::web_utils::bucket_urls::bucket_url_string_from_media_path::bucket_url_string_from_media_path; use crate::state::server_state::ServerState; use crate::util::allowed_explore_media_access::allowed_explore_media_access; -// Cloudflare Ray ID HTTP header name -const CLOUDFLARE_RAY_HEADER: &str = "cf-ray"; - #[derive(Deserialize, ToSchema, IntoParams, Hash, Clone, PartialEq, Eq)] pub struct ListFeaturedMediaFilesQueryParams { pub sort_ascending: Option, @@ -230,7 +228,7 @@ pub async fn list_featured_media_files_handler( let cache_start = Instant::now(); - let maybe_cloudflare_header = get_request_header_optional(&http_request, CLOUDFLARE_RAY_HEADER); + let maybe_cloudflare_header = get_cloudflare_ray_header(&http_request); let maybe_cached_results = server_state .caches diff --git a/crates/service/web/storyteller_web/src/http_server/headers/get_cloudflare_ray_header.rs b/crates/service/web/storyteller_web/src/http_server/headers/get_cloudflare_ray_header.rs new file mode 100644 index 0000000000..31377f3c25 --- /dev/null +++ b/crates/service/web/storyteller_web/src/http_server/headers/get_cloudflare_ray_header.rs @@ -0,0 +1,10 @@ +use actix_web::HttpRequest; +use http_server_common::request::get_request_header_optional::get_request_header_optional; + +// Cloudflare Ray ID HTTP header name +// https://developers.cloudflare.com/fundamentals/reference/cloudflare-ray-id/ +const CLOUDFLARE_RAY_HEADER: &str = "cf-ray"; + +pub fn get_cloudflare_ray_header(request: &HttpRequest) -> Option { + get_request_header_optional(request, CLOUDFLARE_RAY_HEADER) +} diff --git a/crates/service/web/storyteller_web/src/http_server/headers/mod.rs b/crates/service/web/storyteller_web/src/http_server/headers/mod.rs index 6f2dc3a96e..223b5d8d2e 100644 --- a/crates/service/web/storyteller_web/src/http_server/headers/mod.rs +++ b/crates/service/web/storyteller_web/src/http_server/headers/mod.rs @@ -1,2 +1,3 @@ +pub mod get_cloudflare_ray_header; pub mod get_routing_tag_header; pub mod has_debug_header; \ No newline at end of file diff --git a/crates/service/web/storyteller_web/src/http_server/validations/validate_username.rs b/crates/service/web/storyteller_web/src/http_server/validations/validate_username.rs index 14b6be3428..04532d44b4 100644 --- a/crates/service/web/storyteller_web/src/http_server/validations/validate_username.rs +++ b/crates/service/web/storyteller_web/src/http_server/validations/validate_username.rs @@ -1,6 +1,9 @@ use once_cell::sync::Lazy; use regex::Regex; +/// Username may be up to this many characters, but not more. +pub const USERNAME_MAX_LENGTH: usize = 16; + pub fn validate_username(username: &str) -> Result<(), String> { static USERNAME_REGEX: Lazy = Lazy::new(|| { Regex::new(r"^[A-Za-z0-9_\-]{3,16}$").expect("should be valid regex") @@ -10,7 +13,7 @@ pub fn validate_username(username: &str) -> Result<(), String> { return Err("username is too short".to_string()); } - if username.len() > 16 { + if username.len() > USERNAME_MAX_LENGTH { return Err("username is too long".to_string()); } diff --git a/crates/service/web/storyteller_web/src/util/generate_random_username.rs b/crates/service/web/storyteller_web/src/util/generate_random_username.rs new file mode 100644 index 0000000000..8f0301c358 --- /dev/null +++ b/crates/service/web/storyteller_web/src/util/generate_random_username.rs @@ -0,0 +1,65 @@ +use filesys::file_lines::iterate_trimmed_file_lines_without_comments::iterate_trimmed_file_lines_without_comments; +use once_cell::sync::Lazy; +use std::collections::HashSet; +use collections::random_from_array::random_from_array; +use primitives::iterators::iterate_trimmed_lines_without_comments::iterate_trimmed_lines_without_comments; +use crate::http_server::validations::validate_username::USERNAME_MAX_LENGTH; + +pub const ADJECTIVES : &str = include_str!("../../../../../../includes/binary_includes/usernames/atoms/username_adjectives.txt"); +pub const NOUNS : &str = include_str!("../../../../../../includes/binary_includes/usernames/atoms/username_nouns.txt"); +pub const NOUNS_ANIMALS: &str = include_str!("../../../../../../includes/binary_includes/usernames/atoms/username_nouns_animals.txt"); + +fn lines_without_comments() { + +} +static ALL_NOUNS : Lazy> = Lazy::new(|| { + iterate_trimmed_lines_without_comments(NOUNS.lines()) + .chain(iterate_trimmed_lines_without_comments(NOUNS_ANIMALS.lines())) + .collect::>() + .into_iter() + .collect() +}); + +static ALL_ADJECTIVES : Lazy> = Lazy::new(|| { + iterate_trimmed_lines_without_comments(ADJECTIVES.lines()) + .collect::>() + .into_iter() + .collect::>() +}); + +pub fn generate_random_username() -> String { + for _ in 0..100 { + if let Some(username) = generate_candidate_username() { + return username; + } + } + + "random_username".to_string() +} + +fn generate_candidate_username() -> Option { + let maybe_adjective = random_from_array::(&ALL_ADJECTIVES); + let maybe_noun = random_from_array::(&ALL_NOUNS); + + match (maybe_adjective, maybe_noun) { + (Some(adjective), Some(noun)) => { + let maybe_username = format!("{}{}", adjective, noun); + if maybe_username.len() > USERNAME_MAX_LENGTH { + println!("too long: {}", maybe_username); + return None; + } + Some(maybe_username) + }, + _ => None + } +} + +#[cfg(test)] +mod tests { + use crate::util::generate_random_username::generate_random_username; + + #[test] + fn test() { + assert_eq!(generate_random_username(), "asdf".to_string()); + } +} diff --git a/crates/service/web/storyteller_web/src/util/mod.rs b/crates/service/web/storyteller_web/src/util/mod.rs index 126b5f40dd..87caecd40f 100644 --- a/crates/service/web/storyteller_web/src/util/mod.rs +++ b/crates/service/web/storyteller_web/src/util/mod.rs @@ -7,6 +7,7 @@ pub mod delete_role_disambiguation; pub mod email_to_gravatar; pub mod encrypted_sort_id; pub mod enroll_in_studio; +pub mod generate_random_username; pub mod placeholder_images; pub mod read_toml_file_to_struct; pub mod title_to_url_slug;