diff --git a/Cargo.lock b/Cargo.lock index c9aa95f..0cca780 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1416,21 +1416,6 @@ version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" -[[package]] -name = "handlebars" -version = "4.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faa67bab9ff362228eb3d00bd024a4965d8231bbb7921167f0cfa66c6626b225" -dependencies = [ - "log", - "pest", - "pest_derive", - "serde", - "serde_json", - "thiserror", - "walkdir", -] - [[package]] name = "hashbrown" version = "0.12.3" @@ -1925,6 +1910,30 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" +[[package]] +name = "maud" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0bab19cef8a7fe1c18a43e881793bfc9d4ea984befec3ae5bd0415abf3ecf00" +dependencies = [ + "actix-web", + "futures-util", + "itoa 1.0.9", + "maud_macros", +] + +[[package]] +name = "maud_macros" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0be95d66c3024ffce639216058e5bae17a83ecaf266ffc6e4d060ad447c9eed2" +dependencies = [ + "proc-macro-error", + "proc-macro2 1.0.69", + "quote 1.0.33", + "syn 1.0.109", +] + [[package]] name = "maybe-uninit" version = "2.0.0" @@ -2329,51 +2338,6 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" -[[package]] -name = "pest" -version = "2.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae9cee2a55a544be8b89dc6848072af97a20f2422603c10865be2a42b580fff5" -dependencies = [ - "memchr", - "thiserror", - "ucd-trie", -] - -[[package]] -name = "pest_derive" -version = "2.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81d78524685f5ef2a3b3bd1cafbc9fcabb036253d9b1463e726a91cd16e2dfc2" -dependencies = [ - "pest", - "pest_generator", -] - -[[package]] -name = "pest_generator" -version = "2.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68bd1206e71118b5356dae5ddc61c8b11e28b09ef6a31acbd15ea48a28e0c227" -dependencies = [ - "pest", - "pest_meta", - "proc-macro2 1.0.69", - "quote 1.0.33", - "syn 2.0.39", -] - -[[package]] -name = "pest_meta" -version = "2.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c747191d4ad9e4a4ab9c8798f1e82a39affe7ef9648390b7e5548d18e099de6" -dependencies = [ - "once_cell", - "pest", - "sha2", -] - [[package]] name = "phf" version = "0.7.24" @@ -2548,6 +2512,30 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2 1.0.69", + "quote 1.0.33", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2 1.0.69", + "quote 1.0.33", + "version_check", +] + [[package]] name = "proc-macro2" version = "0.4.30" @@ -3223,17 +3211,6 @@ dependencies = [ "digest", ] -[[package]] -name = "sha2" -version = "0.10.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" -dependencies = [ - "cfg-if 1.0.0", - "cpufeatures", - "digest", -] - [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -3504,26 +3481,6 @@ dependencies = [ "utf-8", ] -[[package]] -name = "thiserror" -version = "1.0.50" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.50" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" -dependencies = [ - "proc-macro2 1.0.69", - "quote 1.0.33", - "syn 2.0.39", -] - [[package]] name = "thousands" version = "0.2.0" @@ -3855,12 +3812,6 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" -[[package]] -name = "ucd-trie" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" - [[package]] name = "unicase" version = "2.7.0" @@ -4104,9 +4055,9 @@ dependencies = [ "error-stack", "fake-useragent", "futures 0.3.29", - "handlebars", "lightningcss", "log", + "maud", "md5", "mimalloc", "mini-moka", diff --git a/Cargo.toml b/Cargo.toml index 3e33021..b3f1521 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ reqwest = {version="0.11.22", default-features=false, features=["rustls-tls","br tokio = {version="1.32.0",features=["rt-multi-thread","macros"], default-features = false} serde = {version="1.0.190", default-features=false, features=["derive"]} serde_json = {version="1.0.108", default-features=false} -handlebars = { version = "4.4.0", features = ["dir_source"], default-features = false } +maud = {version="0.25.0", default-features=false, features=["actix-web"]} scraper = {version="0.18.1", default-features = false} actix-web = {version="4.4.0", features = ["cookies", "macros"], default-features=false} actix-files = {version="0.6.2", default-features=false} diff --git a/public/templates/404.html b/public/templates/404.html deleted file mode 100644 index a8a9ac7..0000000 --- a/public/templates/404.html +++ /dev/null @@ -1,10 +0,0 @@ -{{>header this}} -
- Image of broken robot. -
-

Aw! snap

-

404 Page Not Found!

-

Go to search page

-
-
-{{>footer}} diff --git a/public/templates/about.html b/public/templates/about.html deleted file mode 100644 index 9c4cbb0..0000000 --- a/public/templates/about.html +++ /dev/null @@ -1,29 +0,0 @@ -{{>header this}} -
-
-
-

Websurfx

-
-
-

A modern-looking, lightning-fast, privacy-respecting, secure meta search engine written in Rust. It provides a fast and secure search experience while respecting user privacy.
It aggregates results from multiple search engines and presents them in an unbiased manner, filtering out trackers and ads. -

- -

Some of the Top Features:

- - - - - - - - - - - - -
- -

Devoloped by: Websurfx team

-
-{{>footer}} - diff --git a/public/templates/bar.html b/public/templates/bar.html deleted file mode 100644 index 489b075..0000000 --- a/public/templates/bar.html +++ /dev/null @@ -1,3 +0,0 @@ - - - -{{>footer}} diff --git a/public/templates/navbar.html b/public/templates/navbar.html deleted file mode 100644 index c369739..0000000 --- a/public/templates/navbar.html +++ /dev/null @@ -1,6 +0,0 @@ - diff --git a/public/templates/search.html b/public/templates/search.html deleted file mode 100644 index c6c9d6a..0000000 --- a/public/templates/search.html +++ /dev/null @@ -1,86 +0,0 @@ -{{>header this.style}} -
- {{>search_bar this}} -
- {{#if results}} {{#each results}} -
-

{{{this.title}}}

- {{{this.url}}} -

{{{this.description}}}

-
- {{#each engine}} - {{{this}}} - {{/each}} -
-
- {{/each}} {{else}} {{#if disallowed}} -
-
-

- Your search - {{{this.pageQuery}}} - - has been disallowed. -

-

Dear user,

-

- The query - {{{this.pageQuery}}} - has - been blacklisted via server configuration and hence disallowed by the - server. Henceforth no results could be displayed for your query. -

-
- Image of a Barricade -
- {{else}} {{#if filtered}} -
-
-

- Your search - {{{this.pageQuery}}} - - has been filtered. -

-

Dear user,

-

- All the search results contain results that has been configured to be - filtered out via server configuration and henceforth has been - completely filtered out. -

-
- Image of a paper inside a funnel -
- {{else}} {{#if noEnginesSelected}} -
-
-

- No results could be fetched for your search "{{{this.pageQuery}}}" . -

-

Dear user,

-

- No results could be retrieved from the upstream search engines as no - upstream search engines were selected from the settings page. -

-
- Image of a white cross inside a red circle -
- {{else}} -
-

Your search - {{{this.pageQuery}}} - did not match any documents.

-

Suggestions:

-
    -
  • Make sure that all words are spelled correctly.
  • -
  • Try different keywords.
  • -
  • Try more general keywords.
  • -
- Man fishing gif -
- {{/if}} {{/if}} {{/if}} {{/if}} -
- -
- - - - -{{>footer}} diff --git a/public/templates/search_bar.html b/public/templates/search_bar.html deleted file mode 100644 index a006d89..0000000 --- a/public/templates/search_bar.html +++ /dev/null @@ -1,36 +0,0 @@ -
- {{>bar this}} -
- {{#if engineErrorsInfo}} - - - {{else}} - - - {{/if}} -
-
-
- -
- diff --git a/public/templates/settings.html b/public/templates/settings.html deleted file mode 100644 index 3c97213..0000000 --- a/public/templates/settings.html +++ /dev/null @@ -1,22 +0,0 @@ -{{>header this}} -
-

Settings

-
-
- -
- {{> general_tab}} {{> user_interface_tab}} {{> engines_tab}} {{> - cookies_tab}} -

- -
-
-
- - -{{>footer}} diff --git a/public/templates/user_interface_tab.html b/public/templates/user_interface_tab.html deleted file mode 100644 index 7de0f06..0000000 --- a/public/templates/user_interface_tab.html +++ /dev/null @@ -1,28 +0,0 @@ -
-

User Interface

-

select theme

-

- Select the theme from the available themes to be used in user interface -

- -

select color scheme

-

- Select the color scheme for your theme to be used in user interface -

- -
diff --git a/src/config/parser.rs b/src/config/parser.rs index 0acdd25..1b8ba16 100644 --- a/src/config/parser.rs +++ b/src/config/parser.rs @@ -3,7 +3,6 @@ use crate::handler::paths::{file_path, FileType}; -use crate::models::engine_models::{EngineError, EngineHandler}; use crate::models::parser_models::{AggregatorConfig, RateLimiter, Style}; use log::LevelFilter; use mlua::Lua; @@ -29,7 +28,7 @@ pub struct Config { /// It stores the option to whether enable or disable debug mode. pub debug: bool, /// It stores all the engine names that were enabled by the user. - pub upstream_search_engines: Vec, + pub upstream_search_engines: HashMap, /// It stores the time (secs) which controls the server request timeout. pub request_timeout: u8, /// It stores the number of threads which controls the app will use to run. @@ -109,11 +108,7 @@ impl Config { logging, debug, upstream_search_engines: globals - .get::<_, HashMap>("upstream_search_engines")? - .into_iter() - .filter_map(|(key, value)| value.then_some(key)) - .map(|engine| EngineHandler::new(&engine)) - .collect::, error_stack::Report>>()?, + .get::<_, HashMap>("upstream_search_engines")?, request_timeout: globals.get::<_, u8>("request_timeout")?, threads, rate_limiter: RateLimiter { diff --git a/src/lib.rs b/src/lib.rs index 73e9364..1d245fb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,6 +12,7 @@ pub mod handler; pub mod models; pub mod results; pub mod server; +pub mod templates; use std::net::TcpListener; @@ -23,7 +24,6 @@ use actix_governor::{Governor, GovernorConfigBuilder}; use actix_web::{dev::Server, http::header, middleware::Logger, web, App, HttpServer}; use cache::cacher::{Cache, SharedCache}; use config::parser::Config; -use handlebars::Handlebars; use handler::paths::{file_path, FileType}; /// Runs the web server on the provided TCP listener and returns a `Server` instance. @@ -48,16 +48,8 @@ use handler::paths::{file_path, FileType}; /// let server = run(listener,config,cache).expect("Failed to start server"); /// ``` pub fn run(listener: TcpListener, config: Config, cache: Cache) -> std::io::Result { - let mut handlebars: Handlebars<'_> = Handlebars::new(); - let public_folder_path: &str = file_path(FileType::Theme)?; - handlebars - .register_templates_directory(".html", format!("{}/templates", public_folder_path)) - .unwrap(); - - let handlebars_ref: web::Data> = web::Data::new(handlebars); - let cloned_config_threads_opt: u8 = config.threads; let cache = web::Data::new(SharedCache::new(cache)); @@ -75,7 +67,6 @@ pub fn run(listener: TcpListener, config: Config, cache: Cache) -> std::io::Resu App::new() .wrap(Logger::default()) // added logging middleware for logging. - .app_data(handlebars_ref.clone()) .app_data(web::Data::new(config.clone())) .app_data(cache.clone()) .wrap(cors) diff --git a/src/models/aggregation_models.rs b/src/models/aggregation_models.rs index 660804e..680d222 100644 --- a/src/models/aggregation_models.rs +++ b/src/models/aggregation_models.rs @@ -1,11 +1,10 @@ //! This module provides public models for handling, storing and serializing of search results //! data scraped from the upstream search engines. +use super::engine_models::EngineError; use serde::{Deserialize, Serialize}; use smallvec::SmallVec; -use super::{engine_models::EngineError, parser_models::Style}; - /// A named struct to store the raw scraped search results scraped search results from the /// upstream search engines before aggregating it.It derives the Clone trait which is needed /// to write idiomatic rust using `Iterators`. @@ -109,10 +108,6 @@ impl EngineErrorInfo { pub struct SearchResults { /// Stores the individual serializable `SearchResult` struct into a vector of pub results: Vec, - /// Stores the current pages search query `q` provided in the search url. - pub page_query: String, - /// Stores the theming options for the website. - pub style: Style, /// Stores the information on which engines failed with their engine name /// and the type of error that caused it. pub engine_errors_info: Vec, @@ -142,15 +137,9 @@ impl SearchResults { /// the search url. /// * `engine_errors_info` - Takes an array of structs which contains information regarding /// which engines failed with their names, reason and their severity color name. - pub fn new( - results: Vec, - page_query: &str, - engine_errors_info: &[EngineErrorInfo], - ) -> Self { + pub fn new(results: Vec, engine_errors_info: &[EngineErrorInfo]) -> Self { Self { results, - page_query: page_query.to_owned(), - style: Style::default(), engine_errors_info: engine_errors_info.to_owned(), disallowed: Default::default(), filtered: Default::default(), @@ -159,21 +148,11 @@ impl SearchResults { } } - /// A setter function to add website style to the return search results. - pub fn add_style(&mut self, style: &Style) { - self.style = style.clone(); - } - /// A setter function that sets disallowed to true. pub fn set_disallowed(&mut self) { self.disallowed = true; } - /// A setter function to set the current page search query. - pub fn set_page_query(&mut self, page: &str) { - self.page_query = page.to_owned(); - } - /// A setter function that sets the filtered to true. pub fn set_filtered(&mut self) { self.filtered = true; diff --git a/src/models/parser_models.rs b/src/models/parser_models.rs index 9dad348..a669bd6 100644 --- a/src/models/parser_models.rs +++ b/src/models/parser_models.rs @@ -1,8 +1,6 @@ //! This module provides public models for handling, storing and serializing parsed config file //! options from config.lua by grouping them together. -use serde::{Deserialize, Serialize}; - /// A named struct which stores,deserializes, serializes and groups the parsed config file options /// of theme and colorscheme names into the Style struct which derives the `Clone`, `Serialize` /// and Deserialize traits where the `Clone` trait is derived for allowing the struct to be @@ -12,7 +10,7 @@ use serde::{Deserialize, Serialize}; /// order to allow the deserializing the json back to struct in aggregate function in /// aggregator.rs and create a new struct out of it and then serialize it back to json and pass /// it to the template files. -#[derive(Serialize, Deserialize, Clone, Default)] +#[derive(Clone, Default)] pub struct Style { /// It stores the parsed theme option used to set a theme for the website. pub theme: String, diff --git a/src/results/aggregator.rs b/src/results/aggregator.rs index 3285786..5c53864 100644 --- a/src/results/aggregator.rs +++ b/src/results/aggregator.rs @@ -180,7 +180,7 @@ pub async fn aggregate( let results: Vec = result_map.into_values().collect(); - Ok(SearchResults::new(results, query, &engine_errors_info)) + Ok(SearchResults::new(results, &engine_errors_info)) } /// Filters a map of search results using a list of regex patterns. diff --git a/src/server/router.rs b/src/server/router.rs index 69a3ede..31b7e6a 100644 --- a/src/server/router.rs +++ b/src/server/router.rs @@ -7,30 +7,30 @@ use crate::{ handler::paths::{file_path, FileType}, }; use actix_web::{get, web, HttpRequest, HttpResponse}; -use handlebars::Handlebars; use std::fs::read_to_string; /// Handles the route of index page or main page of the `websurfx` meta search engine website. #[get("/")] -pub async fn index( - hbs: web::Data>, - config: web::Data, -) -> Result> { - let page_content: String = hbs.render("index", &config.style).unwrap(); - Ok(HttpResponse::Ok().body(page_content)) +pub async fn index(config: web::Data) -> Result> { + Ok(HttpResponse::Ok().body( + crate::templates::views::index::index(&config.style.colorscheme, &config.style.theme).0, + )) } /// Handles the route of any other accessed route/page which is not provided by the /// website essentially the 404 error page. pub async fn not_found( - hbs: web::Data>, config: web::Data, ) -> Result> { - let page_content: String = hbs.render("404", &config.style)?; - Ok(HttpResponse::Ok() .content_type("text/html; charset=utf-8") - .body(page_content)) + .body( + crate::templates::views::not_found::not_found( + &config.style.colorscheme, + &config.style.theme, + ) + .0, + )) } /// Handles the route of robots.txt page of the `websurfx` meta search engine website. @@ -45,20 +45,26 @@ pub async fn robots_data(_req: HttpRequest) -> Result>, - config: web::Data, -) -> Result> { - let page_content: String = hbs.render("about", &config.style)?; - Ok(HttpResponse::Ok().body(page_content)) +pub async fn about(config: web::Data) -> Result> { + Ok(HttpResponse::Ok().body( + crate::templates::views::about::about(&config.style.colorscheme, &config.style.theme).0, + )) } /// Handles the route of settings page of the `websurfx` meta search engine website. #[get("/settings")] pub async fn settings( - hbs: web::Data>, config: web::Data, ) -> Result> { - let page_content: String = hbs.render("settings", &config.style)?; - Ok(HttpResponse::Ok().body(page_content)) + Ok(HttpResponse::Ok().body( + crate::templates::views::settings::settings( + &config.style.colorscheme, + &config.style.theme, + &config + .upstream_search_engines + .keys() + .collect::>(), + )? + .0, + )) } diff --git a/src/server/routes/search.rs b/src/server/routes/search.rs index 9dbd1e1..35c9cc4 100644 --- a/src/server/routes/search.rs +++ b/src/server/routes/search.rs @@ -6,13 +6,12 @@ use crate::{ handler::paths::{file_path, FileType}, models::{ aggregation_models::SearchResults, - engine_models::EngineHandler, + engine_models::{EngineError, EngineHandler}, server_models::{Cookie, SearchParams}, }, results::aggregator::aggregate, }; use actix_web::{get, web, HttpRequest, HttpResponse}; -use handlebars::Handlebars; use regex::Regex; use std::{ fs::File, @@ -20,19 +19,6 @@ use std::{ }; use tokio::join; -/// Handles the route of any other accessed route/page which is not provided by the -/// website essentially the 404 error page. -pub async fn not_found( - hbs: web::Data>, - config: web::Data, -) -> Result> { - let page_content: String = hbs.render("404", &config.style)?; - - Ok(HttpResponse::Ok() - .content_type("text/html; charset=utf-8") - .body(page_content)) -} - /// Handles the route of search page of the `websurfx` meta search engine website and it takes /// two search url parameters `q` and `page` where `page` parameter is optional. /// @@ -49,7 +35,6 @@ pub async fn not_found( /// ``` #[get("/search")] pub async fn search( - hbs: web::Data>, req: HttpRequest, config: web::Data, cache: web::Data, @@ -58,7 +43,7 @@ pub async fn search( match ¶ms.q { Some(query) => { if query.trim().is_empty() { - return Ok(HttpResponse::Found() + return Ok(HttpResponse::TemporaryRedirect() .insert_header(("location", "/")) .finish()); } @@ -112,10 +97,17 @@ pub async fn search( ) ); - let page_content: String = hbs.render("search", &results?)?; - Ok(HttpResponse::Ok().body(page_content)) + Ok(HttpResponse::Ok().body( + crate::templates::views::search::search( + &config.style.colorscheme, + &config.style.theme, + query, + &results?, + ) + .0, + )) } - None => Ok(HttpResponse::Found() + None => Ok(HttpResponse::TemporaryRedirect() .insert_header(("location", "/")) .finish()), } @@ -171,8 +163,6 @@ async fn results( if _flag { results.set_disallowed(); - results.add_style(&config.style); - results.set_page_query(query); cache.cache_results(&results, &url).await?; results.set_safe_search_level(safe_search_level); return Ok(results); @@ -221,23 +211,27 @@ async fn results( true => { let mut search_results = SearchResults::default(); search_results.set_no_engines_selected(); - search_results.set_page_query(query); search_results } } } - None => { - aggregate( - query, - page, - config.aggregator.random_delay, - config.debug, - &config.upstream_search_engines, - config.request_timeout, - safe_search_level, - ) - .await? - } + None => aggregate( + query, + page, + config.aggregator.random_delay, + config.debug, + &config + .upstream_search_engines + .clone() + .into_iter() + .filter_map(|(key, value)| value.then_some(key)) + .map(|engine| EngineHandler::new(&engine)) + .collect::, error_stack::Report>>( + )?, + config.request_timeout, + safe_search_level, + ) + .await?, }; if results.engine_errors_info().is_empty() && results.results().is_empty() @@ -245,7 +239,6 @@ async fn results( { results.set_filtered(); } - results.add_style(&config.style); cache .cache_results(&results, &(format!("{url}{safe_search_level}"))) .await?; diff --git a/src/templates/mod.rs b/src/templates/mod.rs new file mode 100644 index 0000000..bc39dce --- /dev/null +++ b/src/templates/mod.rs @@ -0,0 +1,5 @@ +//! This module provides other modules to handle both the view and its partials for the `websurfx` +//! search engine frontend. + +mod partials; +pub mod views; diff --git a/src/templates/partials/bar.rs b/src/templates/partials/bar.rs new file mode 100644 index 0000000..13f78a8 --- /dev/null +++ b/src/templates/partials/bar.rs @@ -0,0 +1,21 @@ +//! A module that handles `bar` partial for the `search_bar` partial and the home/index/main page in the `websurfx` frontend. + +use maud::{html, Markup, PreEscaped}; + +/// A functions that handles the html code for the bar for the `search_bar` partial and the +/// home/index/main page in the search engine frontend. +/// +/// # Arguments +/// +/// * `query` - It takes the current search query provided by user as an argument. +/// +/// # Returns +/// +/// It returns the compiled html code for the search bar as a result. +pub fn bar(query: &str) -> Markup { + html!( + (PreEscaped("
")) + input type="search" name="search-box" value=(query) placeholder="Type to search"; + button type="submit" onclick="searchWeb()"{"search"} + ) +} diff --git a/src/templates/partials/footer.rs b/src/templates/partials/footer.rs new file mode 100644 index 0000000..beaa8ca --- /dev/null +++ b/src/templates/partials/footer.rs @@ -0,0 +1,29 @@ +//! A module that handles the footer for all the pages in the `websurfx` frontend. + +use maud::{html, Markup, PreEscaped}; + +/// A functions that handles the html code for the footer for all the pages in the search engine +/// frontend. +/// +/// # Returns +/// +/// It returns the compiled html code for the footer as a result. +pub fn footer() -> Markup { + html!( + footer{ + div{ + span{"Powered By "b{"Websurfx"}}span{"-"}span{"a lightening fast, privacy respecting, secure meta + search engine"} + } + div{ + ul{ + li{a href="https://github.com/neon-mmd/websurfx"{"Source Code"}} + li{a href="https://github.com/neon-mmd/websurfx/issues"{"Issues/Bugs"}} + } + } + } + script src="static/settings.js"{} + (PreEscaped("")) + (PreEscaped("")) + ) +} diff --git a/src/templates/partials/header.rs b/src/templates/partials/header.rs new file mode 100644 index 0000000..c477aec --- /dev/null +++ b/src/templates/partials/header.rs @@ -0,0 +1,35 @@ +//! A module that handles the header for all the pages in the `websurfx` frontend. + +use crate::templates::partials::navbar::navbar; +use maud::{html, Markup, PreEscaped, DOCTYPE}; + +/// A function that handles the html code for the header for all the pages in the search engine frontend. +/// +/// # Arguments +/// +/// * `colorscheme` - It takes the colorscheme name as an argument. +/// * `theme` - It takes the theme name as an argument. +/// +/// # Returns +/// +/// It returns the compiled html markup code for the header as a result. +pub fn header(colorscheme: &str, theme: &str) -> Markup { + html!( + (DOCTYPE) + html lang="en" + + head{ + title{"Websurfx"} + meta charset="UTF-8"; + meta name="viewport" content="width=device-width, initial-scale=1"; + link href=(format!("static/colorschemes/{colorscheme}.css")) rel="stylesheet" type="text/css"; + link href=(format!("static/themes/{theme}.css")) rel="stylesheet" type="text/css"; + } + + (PreEscaped("")) + header{ + h1{a href="/"{"Websurfx"}} + (navbar()) + } + ) +} diff --git a/src/templates/partials/mod.rs b/src/templates/partials/mod.rs new file mode 100644 index 0000000..0e6b803 --- /dev/null +++ b/src/templates/partials/mod.rs @@ -0,0 +1,8 @@ +//! This module provides other modules to handle the partials for the views in the `websurfx` frontend. + +pub mod bar; +pub mod footer; +pub mod header; +pub mod navbar; +pub mod search_bar; +pub mod settings_tabs; diff --git a/src/templates/partials/navbar.rs b/src/templates/partials/navbar.rs new file mode 100644 index 0000000..f1f86fd --- /dev/null +++ b/src/templates/partials/navbar.rs @@ -0,0 +1,19 @@ +//! A module that handles `navbar` partial for the header partial in the `websurfx` frontend. + +use maud::{html, Markup}; + +/// A functions that handles the html code for the header partial. +/// +/// # Returns +/// +/// It returns the compiled html code for the navbar as a result. +pub fn navbar() -> Markup { + html!( + nav{ + ul{ + li{a href="about"{"about"}} + li{a href="settings"{"settings"}} + } + } + ) +} diff --git a/src/templates/partials/search_bar.rs b/src/templates/partials/search_bar.rs new file mode 100644 index 0000000..c40244c --- /dev/null +++ b/src/templates/partials/search_bar.rs @@ -0,0 +1,76 @@ +//! A module that handles `search bar` partial for the search page in the `websurfx` frontend. + +use maud::{html, Markup, PreEscaped}; + +use crate::{models::aggregation_models::EngineErrorInfo, templates::partials::bar::bar}; + +/// A constant holding the named safe search level options for the corresponding values 0, 1 and 2. +const SAFE_SEARCH_LEVELS_NAME: [&str; 3] = ["None", "Low", "Moderate"]; + +/// A functions that handles the html code for the search bar for the search page. +/// +/// # Arguments +/// +/// * `engine_errors_info` - It takes the engine errors list containing errors for each upstream +/// search engine which failed to provide results as an argument. +/// * `safe_search_level` - It takes the safe search level with values from 0-2 as an argument. +/// * `query` - It takes the current search query provided by user as an argument. +/// +/// # Returns +/// +/// It returns the compiled html code for the search bar as a result. +pub fn search_bar( + engine_errors_info: &[EngineErrorInfo], + safe_search_level: u8, + query: &str, +) -> Markup { + html!( + .search_area{ + (bar(query)) + .error_box { + @if !engine_errors_info.is_empty(){ + button onclick="toggleErrorBox()" class="error_box_toggle_button"{ + img src="./images/warning.svg" alt="Info icon for error box"; + } + .dropdown_error_box{ + @for errors in engine_errors_info{ + .error_item{ + span class="engine_name"{(errors.engine)} + span class="engine_name"{(errors.error)} + span class="severity_color" style="background: {{{this.severity_color}}};"{} + } + } + } + } + @else { + button onclick="toggleErrorBox()" class="error_box_toggle_button"{ + img src="./images/info.svg" alt="Warning icon for error box"; + } + .dropdown_error_box { + .no_errors{ + "Everything looks good 🙂!!" + } + } + } + } + (PreEscaped("
")) + .search_options { + @if safe_search_level >= 3 { + (PreEscaped("")) + } + @for (idx, name) in SAFE_SEARCH_LEVELS_NAME.iter().enumerate() { + @if (safe_search_level as usize) == idx { + option value=(idx) selected {(format!("SafeSearch: {name}"))} + } + @else{ + option value=(idx) {(format!("SafeSearch: {name}"))} + } + } + (PreEscaped("")) + } + } + ) +} diff --git a/src/templates/partials/settings_tabs/cookies.rs b/src/templates/partials/settings_tabs/cookies.rs new file mode 100644 index 0000000..c8bc8e6 --- /dev/null +++ b/src/templates/partials/settings_tabs/cookies.rs @@ -0,0 +1,25 @@ +//! A module that handles the engines tab for setting page view in the `websurfx` frontend. + +use maud::{html, Markup}; + +/// A functions that handles the html code for the cookies tab for the settings page for the search page. +/// +/// # Returns +/// +/// It returns the compiled html markup code for the cookies tab. +pub fn cookies() -> Markup { + html!( + div class="cookies tab"{ + h1{"Cookies"} + p class="description"{ + "This is the cookies are saved on your system and it contains the preferences + you chose in the settings page" + } + input type="text" name="cookie_field" value="" readonly; + p class="description"{ + "The cookies stored are not used by us for any malicious intend or for + tracking you in any way." + } + } + ) +} diff --git a/src/templates/partials/settings_tabs/engines.rs b/src/templates/partials/settings_tabs/engines.rs new file mode 100644 index 0000000..5394178 --- /dev/null +++ b/src/templates/partials/settings_tabs/engines.rs @@ -0,0 +1,43 @@ +//! A module that handles the engines tab for setting page view in the `websurfx` frontend. + +use maud::{html, Markup}; + +/// A functions that handles the html code for the engines tab for the settings page for the search page. +/// +/// # Arguments +/// +/// * `engine_names` - It takes the list of all available engine names as an argument. +/// +/// # Returns +/// +/// It returns the compiled html markup code for the engines tab. +pub fn engines(engine_names: &[&String]) -> Markup { + html!( + div class="engines tab"{ + h1{"Engines"} + h3{"select search engines"} + p class="description"{ + "Select the search engines from the list of engines that you want results from" + } + .engine_selection{ + .toggle_btn{ + label class="switch"{ + input type="checkbox" class="select_all" onchange="toggleAllSelection()"; + span class="slider round"{} + } + "Select All" + } + hr; + @for engine_name in engine_names{ + .toggle_btn{ + label class="switch"{ + input type="checkbox" class="engine"; + span class="slider round"{} + } + (format!("{}{}",engine_name[..1].to_uppercase().to_owned(), engine_name[1..].to_owned())) + } + } + } + } + ) +} diff --git a/src/templates/partials/settings_tabs/general.rs b/src/templates/partials/settings_tabs/general.rs new file mode 100644 index 0000000..4b0043d --- /dev/null +++ b/src/templates/partials/settings_tabs/general.rs @@ -0,0 +1,28 @@ +//! A module that handles the general tab for setting page view in the `websurfx` frontend. + +use maud::{html, Markup}; + +/// A constant holding the named safe search level options for the corresponding values 0, 1 and 2. +const SAFE_SEARCH_LEVELS: [(u8, &str); 3] = [(0, "None"), (1, "Low"), (2, "Moderate")]; + +/// A functions that handles the html code for the general tab for the settings page for the search page. +/// +/// # Returns +/// +/// It returns the compiled html markup code for the general tab. +pub fn general() -> Markup { + html!( + div class="general tab active"{ + h1{"General"} + h3{"Select a safe search level"} + p class="description"{ + "Select a safe search level from the menu below to filter content based on the level." + } + select name="safe_search_levels"{ + @for (k,v) in SAFE_SEARCH_LEVELS{ + option value=(k){(v)} + } + } + } + ) +} diff --git a/src/templates/partials/settings_tabs/mod.rs b/src/templates/partials/settings_tabs/mod.rs new file mode 100644 index 0000000..84973d9 --- /dev/null +++ b/src/templates/partials/settings_tabs/mod.rs @@ -0,0 +1,7 @@ +//! This module provides other modules to handle the partials for the tabs for the settings page +//! view in the `websurfx` frontend. + +pub mod cookies; +pub mod engines; +pub mod general; +pub mod user_interface; diff --git a/src/templates/partials/settings_tabs/user_interface.rs b/src/templates/partials/settings_tabs/user_interface.rs new file mode 100644 index 0000000..90e1ce9 --- /dev/null +++ b/src/templates/partials/settings_tabs/user_interface.rs @@ -0,0 +1,65 @@ +//! A module that handles the user interface tab for setting page view in the `websurfx` frontend. + +use crate::handler::paths::{file_path, FileType}; +use maud::{html, Markup}; +use std::fs::read_dir; + +/// A helper function that helps in building the list of all available colorscheme/theme names +/// present in the colorschemes and themes folder respectively. +/// +/// # Arguments +/// +/// * `style_type` - It takes the style type of the values `theme` and `colorscheme` as an +/// argument. +/// +/// # Error +/// +/// Returns a list of colorscheme/theme names as a vector of tuple strings on success otherwise +/// returns a standard error message. +fn style_option_list( + style_type: &str, +) -> Result, Box> { + let mut style_option_names: Vec<(String, String)> = Vec::new(); + for file in read_dir(format!( + "{}static/{}/", + file_path(FileType::Theme)?, + style_type, + ))? { + let style_name = file?.file_name().to_str().unwrap().replace(".css", ""); + style_option_names.push((style_name.clone(), style_name.replace('-', " "))); + } + + Ok(style_option_names) +} + +/// A functions that handles the html code for the user interface tab for the settings page for the search page. +/// +/// # Error +/// +/// It returns the compiled html markup code for the user interface tab on success otherwise +/// returns a standard error message. +pub fn user_interface() -> Result> { + Ok(html!( + div class="user_interface tab"{ + h1{"User Interface"} + h3{"select theme"} + p class="description"{ + "Select the theme from the available themes to be used in user interface" + } + select name="themes"{ + @for (k,v) in style_option_list("themes")?{ + option value=(k){(v)} + } + } + h3{"select color scheme"} + p class="description"{ + "Select the color scheme for your theme to be used in user interface" + } + select name="colorschemes"{ + @for (k,v) in style_option_list("colorschemes")?{ + option value=(k){(v)} + } + } + } + )) +} diff --git a/src/templates/views/about.rs b/src/templates/views/about.rs new file mode 100644 index 0000000..9ee2d6d --- /dev/null +++ b/src/templates/views/about.rs @@ -0,0 +1,48 @@ +//! A module that handles the view for the about page in the `websurfx` frontend. + +use maud::{html, Markup}; + +use crate::templates::partials::{footer::footer, header::header}; + +/// A function that handles the html code for the about page view in the search engine frontend. +/// +/// # Arguments +/// +/// * `colorscheme` - It takes the colorscheme name as an argument. +/// * `theme` - It takes the theme name as an argument. +/// +/// # Returns +/// +/// It returns the compiled html markup code as a result. +pub fn about(colorscheme: &str, theme: &str) -> Markup { + html!( + (header(colorscheme, theme)) + main class="about-container"{ + article { + div{ + h1{"Websurfx"} + hr size="4" width="100%" color="#a6e3a1"{} + } + p{"A modern-looking, lightning-fast, privacy-respecting, secure meta search engine written in Rust. It provides a fast and secure search experience while respecting user privacy."br{}" It aggregates results from multiple search engines and presents them in an unbiased manner, filtering out trackers and ads." + } + + h2{"Some of the Top Features:"} + + ul{strong{"Lightning fast "}"- Results load within milliseconds for an instant search experience."} + + ul{strong{"Secure search"}" - All searches are performed over an encrypted connection to prevent snooping."} + + ul{strong{"Ad free results"}" - All search results are ad free and clutter free for a clean search experience."} + + ul{strong{"Privacy focused"}" - Websurfx does not track, store or sell your search data. Your privacy is our priority."} + + ul{strong{"Free and Open source"}" - The entire project's code is open source and available for free on "{a href="https://github.com/neon-mmd/websurfx"{"GitHub"}}" under an GNU Affero General Public License."} + + ul{strong{"Highly customizable"}" - Websurfx comes with 9 built-in color themes and supports creating custom themes effortlessly."} + } + + h3{"Devoloped by: "{a href="https://github.com/neon-mmd/websurfx"{"Websurfx team"}}} + } + (footer()) + ) +} diff --git a/src/templates/views/index.rs b/src/templates/views/index.rs new file mode 100644 index 0000000..3816f22 --- /dev/null +++ b/src/templates/views/index.rs @@ -0,0 +1,28 @@ +//! A module that handles the view for the index/home/main page in the `websurfx` frontend. + +use maud::{html, Markup, PreEscaped}; + +use crate::templates::partials::{bar::bar, footer::footer, header::header}; + +/// A function that handles the html code for the index/html/main page view in the search engine frontend. +/// +/// # Arguments +/// +/// * `colorscheme` - It takes the colorscheme name as an argument. +/// * `theme` - It takes the theme name as an argument. +/// +/// # Returns +/// +/// It returns the compiled html markup code as a result. +pub fn index(colorscheme: &str, theme: &str) -> Markup { + html!( + (header(colorscheme, theme)) + main class="search-container"{ + img src="../images/websurfx_logo.png" alt="Websurfx meta-search engine logo"; + (bar(&String::default())) + (PreEscaped("")) + } + script src="static/index.js"{} + (footer()) + ) +} diff --git a/src/templates/views/mod.rs b/src/templates/views/mod.rs new file mode 100644 index 0000000..bbfe189 --- /dev/null +++ b/src/templates/views/mod.rs @@ -0,0 +1,8 @@ +//! This module provides other modules to handle view for each individual page in the +//! `websurfx` frontend. + +pub mod about; +pub mod index; +pub mod not_found; +pub mod search; +pub mod settings; diff --git a/src/templates/views/not_found.rs b/src/templates/views/not_found.rs new file mode 100644 index 0000000..9e23da9 --- /dev/null +++ b/src/templates/views/not_found.rs @@ -0,0 +1,29 @@ +//! A module that handles the view for the 404 page in the `websurfx` frontend. + +use crate::templates::partials::{footer::footer, header::header}; +use maud::{html, Markup}; + +/// A function that handles the html code for the 404 page view in the search engine frontend. +/// +/// # Arguments +/// +/// * `colorscheme` - It takes the colorscheme name as an argument. +/// * `theme` - It takes the theme name as an argument. +/// +/// # Returns +/// +/// It returns the compiled html markup code as a result. +pub fn not_found(colorscheme: &str, theme: &str) -> Markup { + html!( + (header(colorscheme, theme)) + main class="error_container"{ + img src="images/robot-404.svg" alt="Image of broken robot."; + .error_content{ + h1{"Aw! snap"} + h2{"404 Page Not Found!"} + p{"Go to "{a href="/"{"search page"}}} + } + } + (footer()) + ) +} diff --git a/src/templates/views/search.rs b/src/templates/views/search.rs new file mode 100644 index 0000000..440d585 --- /dev/null +++ b/src/templates/views/search.rs @@ -0,0 +1,122 @@ +//! A module that handles the view for the search page in the `websurfx` frontend. + +use maud::{html, Markup, PreEscaped}; + +use crate::{ + models::aggregation_models::SearchResults, + templates::partials::{footer::footer, header::header, search_bar::search_bar}, +}; + +/// A function that handles the html code for the search page view in the search engine frontend. +/// +/// # Arguments +/// +/// * `colorscheme` - It takes the colorscheme name as an argument. +/// * `theme` - It takes the theme name as an argument. +/// * `query` - It takes the current search query provided by the user as an argument. +/// * `search_results` - It takes the aggregated search results as an argument. +/// +/// # Returns +/// +/// It returns the compiled html markup code as a result. +pub fn search( + colorscheme: &str, + theme: &str, + query: &str, + search_results: &SearchResults, +) -> Markup { + html!( + (header(colorscheme, theme)) + main class="results"{ + (search_bar(&search_results.engine_errors_info, search_results.safe_search_level, query)) + .results_aggregated{ + @if !search_results.results.is_empty() { + @for result in search_results.results.iter(){ + .result { + h1{a href=(result.url){(PreEscaped(&result.title))}} + small{(result.url)} + p{(PreEscaped(&result.description))} + .upstream_engines{ + @for name in result.clone().engine{ + span{(name)} + } + } + } + } + } + @else if search_results.disallowed{ + .result_disallowed{ + .description{ + p{ + "Your search - "{span class="user_query"{(query)}}" - + has been disallowed." + } + p class="description_paragraph"{"Dear user,"} + p class="description_paragraph"{ + "The query - "{span class="user_query"{(query)}}" - has + been blacklisted via server configuration and hence disallowed by the + server. Henceforth no results could be displayed for your query." + } + } + img src="./images/barricade.png" alt="Image of a Barricade"; + } + } + @else if search_results.filtered { + .result_filtered{ + .description{ + p{ + "Your search - "{span class="user_query"{(query)}}" - + has been filtered." + } + p class="description_paragraph"{"Dear user,"} + p class="description_paragraph"{ + "All the search results contain results that has been configured to be + filtered out via server configuration and henceforth has been + completely filtered out." + } + } + img src="./images/filter.png" alt="Image of a paper inside a funnel"; + } + } + @else if search_results.no_engines_selected { + .result_engine_not_selected{ + .description{ + p{ + "No results could be fetched for your search '{span class="user_query"{(query)}}'." + } + p class="description_paragraph"{"Dear user,"} + p class="description_paragraph"{ + "No results could be retrieved from the upstream search engines as no + upstream search engines were selected from the settings page." + } + } + img src="./images/no_selection.png" alt="Image of a white cross inside a red circle"; + } + } + @else{ + .result_not_found { + p{"Your search - "{(query)}" - did not match any documents."} + p class="suggestions"{"Suggestions:"} + ul{ + li{"Make sure that all words are spelled correctly."} + li{"Try different keywords."} + li{"Try more general keywords."} + } + img src="./images/no_results.gif" alt="Man fishing gif"; + } + } + } + .page_navigation { + button type="button" onclick="navigate_backward()"{ + (PreEscaped("←")) "previous" + } + button type="button" onclick="navigate_forward()"{"next" (PreEscaped("→"))} + } + } + script src="static/index.js"{} + script src="static/search_area_options.js"{} + script src="static/pagination.js"{} + script src="static/error_box.js"{} + (footer()) + ) +} diff --git a/src/templates/views/settings.rs b/src/templates/views/settings.rs new file mode 100644 index 0000000..7b69ac7 --- /dev/null +++ b/src/templates/views/settings.rs @@ -0,0 +1,56 @@ +//! A module that handles the view for the settings page in the `websurfx` frontend. + +use maud::{html, Markup}; + +use crate::templates::partials::{ + footer::footer, + header::header, + settings_tabs::{ + cookies::cookies, engines::engines, general::general, user_interface::user_interface, + }, +}; + +/// A function that handles the html code for the settings page view in the search engine frontend. +/// +/// # Arguments +/// +/// * `colorscheme` - It takes the colorscheme name as an argument. +/// * `theme` - It takes the theme name as an argument. +/// * `engine_names` - It takes a list of engine names as an argument. +/// +/// # Error +/// +/// This function returns a compiled html markup code on success otherwise returns a standard error +/// message. +pub fn settings( + colorscheme: &str, + theme: &str, + engine_names: &[&String], +) -> Result> { + Ok(html!( + (header(colorscheme, theme)) + main class="settings"{ + h1{"Settings"} + hr; + .settings_container{ + .sidebar{ + div class="btn active" onclick="setActiveTab(this)"{"general"} + .btn onclick="setActiveTab(this)"{"user interface"} + .btn onclick="setActiveTab(this)"{"engines"} + .btn onclick="setActiveTab(this)"{"cookies"} + } + .main_container{ + (general()) + (user_interface()?) + (engines(engine_names)) + (cookies()) + p class="message"{} + button type="submit" onclick="setClientSettings()"{"Save"} + } + } + } + script src="static/settings.js"{} + script src="static/cookies.js"{} + (footer()) + )) +} diff --git a/tests/index.rs b/tests/index.rs index 91d0814..3bd7381 100644 --- a/tests/index.rs +++ b/tests/index.rs @@ -1,7 +1,6 @@ use std::net::TcpListener; -use handlebars::Handlebars; -use websurfx::{config::parser::Config, run}; +use websurfx::{config::parser::Config, run, templates::views}; // Starts a new instance of the HTTP server, bound to a random available port fn spawn_app() -> String { @@ -21,18 +20,6 @@ fn spawn_app() -> String { format!("http://127.0.0.1:{}/", port) } -// Creates a new instance of Handlebars and registers the templates directory. -// This is used to compare the rendered template with the response body. -fn handlebars() -> Handlebars<'static> { - let mut handlebars = Handlebars::new(); - - handlebars - .register_templates_directory(".html", "./public/templates") - .unwrap(); - - handlebars -} - #[tokio::test] async fn test_index() { let address = spawn_app(); @@ -41,9 +28,8 @@ async fn test_index() { let res = client.get(address).send().await.unwrap(); assert_eq!(res.status(), 200); - let handlebars = handlebars(); let config = Config::parse(true).unwrap(); - let template = handlebars.render("index", &config.style).unwrap(); + let template = views::index::index(&config.style.colorscheme, &config.style.theme).0; assert_eq!(res.text().await.unwrap(), template); }