|
|
|
|
|
use crate::{ |
|
cache::cacher::RedisCache, |
|
config::parser::Config, |
|
handler::paths::{file_path, FileType}, |
|
models::{aggregation_models::SearchResults, engine_models::EngineHandler}, |
|
results::aggregator::aggregate, |
|
}; |
|
use actix_web::{get, web, HttpRequest, HttpResponse}; |
|
use handlebars::Handlebars; |
|
use regex::Regex; |
|
use serde::Deserialize; |
|
use std::{ |
|
fs::{read_to_string, File}, |
|
io::{BufRead, BufReader, Read}, |
|
}; |
|
use tokio::join; |
|
|
|
|
|
|
|
static REDIS_CACHE: async_once_cell::OnceCell<RedisCache> = async_once_cell::OnceCell::new(); |
|
|
|
|
|
#[derive(Deserialize)] |
|
pub struct SearchParams { |
|
|
|
|
|
q: Option<String>, |
|
|
|
|
|
page: Option<u32>, |
|
|
|
|
|
safesearch: Option<u8>, |
|
} |
|
|
|
|
|
#[get("/")] |
|
pub async fn index( |
|
hbs: web::Data<Handlebars<'_>>, |
|
config: web::Data<Config>, |
|
) -> Result<HttpResponse, Box<dyn std::error::Error>> { |
|
let page_content: String = hbs.render("index", &config.style).unwrap(); |
|
Ok(HttpResponse::Ok().body(page_content)) |
|
} |
|
|
|
|
|
|
|
pub async fn not_found( |
|
hbs: web::Data<Handlebars<'_>>, |
|
config: web::Data<Config>, |
|
) -> Result<HttpResponse, Box<dyn std::error::Error>> { |
|
let page_content: String = hbs.render("404", &config.style)?; |
|
|
|
Ok(HttpResponse::Ok() |
|
.content_type("text/html; charset=utf-8") |
|
.body(page_content)) |
|
} |
|
|
|
|
|
#[allow(dead_code)] |
|
#[derive(Deserialize)] |
|
struct Cookie<'a> { |
|
|
|
theme: &'a str, |
|
|
|
colorscheme: &'a str, |
|
|
|
engines: Vec<&'a str>, |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[get("/search")] |
|
pub async fn search( |
|
hbs: web::Data<Handlebars<'_>>, |
|
req: HttpRequest, |
|
config: web::Data<Config>, |
|
) -> Result<HttpResponse, Box<dyn std::error::Error>> { |
|
let params = web::Query::<SearchParams>::from_query(req.query_string())?; |
|
match ¶ms.q { |
|
Some(query) => { |
|
if query.trim().is_empty() { |
|
return Ok(HttpResponse::Found() |
|
.insert_header(("location", "/")) |
|
.finish()); |
|
} |
|
let page = match ¶ms.page { |
|
Some(page) => *page, |
|
None => 1, |
|
}; |
|
|
|
let safe_search: u8 = match config.safe_search { |
|
3..=4 => config.safe_search, |
|
_ => match ¶ms.safesearch { |
|
Some(safesearch) => match safesearch { |
|
0..=2 => *safesearch, |
|
_ => 1, |
|
}, |
|
None => config.safe_search, |
|
}, |
|
}; |
|
|
|
let (_, results, _) = join!( |
|
results( |
|
format!( |
|
"http://{}:{}/search?q={}&page={}&safesearch={}", |
|
config.binding_ip, |
|
config.port, |
|
query, |
|
page - 1, |
|
safe_search |
|
), |
|
&config, |
|
query, |
|
page - 1, |
|
req.clone(), |
|
safe_search |
|
), |
|
results( |
|
format!( |
|
"http://{}:{}/search?q={}&page={}&safesearch={}", |
|
config.binding_ip, config.port, query, page, safe_search |
|
), |
|
&config, |
|
query, |
|
page, |
|
req.clone(), |
|
safe_search |
|
), |
|
results( |
|
format!( |
|
"http://{}:{}/search?q={}&page={}&safesearch={}", |
|
config.binding_ip, |
|
config.port, |
|
query, |
|
page + 1, |
|
safe_search |
|
), |
|
&config, |
|
query, |
|
page + 1, |
|
req.clone(), |
|
safe_search |
|
) |
|
); |
|
|
|
let page_content: String = hbs.render("search", &results?)?; |
|
Ok(HttpResponse::Ok().body(page_content)) |
|
} |
|
None => Ok(HttpResponse::Found() |
|
.insert_header(("location", "/")) |
|
.finish()), |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async fn results( |
|
url: String, |
|
config: &Config, |
|
query: &str, |
|
page: u32, |
|
req: HttpRequest, |
|
safe_search: u8, |
|
) -> Result<SearchResults, Box<dyn std::error::Error>> { |
|
|
|
let mut redis_cache: RedisCache = REDIS_CACHE |
|
.get_or_init(async { |
|
|
|
RedisCache::new(&config.redis_url, 5).await.unwrap() |
|
}) |
|
.await |
|
.clone(); |
|
|
|
let cached_results_json: Result<String, error_stack::Report<crate::cache::error::PoolError>> = |
|
redis_cache.clone().cached_json(&url).await; |
|
|
|
|
|
match cached_results_json { |
|
Ok(results) => Ok(serde_json::from_str::<SearchResults>(&results)?), |
|
Err(_) => { |
|
if safe_search == 4 { |
|
let mut results: SearchResults = SearchResults::default(); |
|
let mut _flag: bool = |
|
is_match_from_filter_list(file_path(FileType::BlockList)?, query)?; |
|
_flag = !is_match_from_filter_list(file_path(FileType::AllowList)?, query)?; |
|
|
|
if _flag { |
|
results.set_disallowed(); |
|
results.add_style(&config.style); |
|
results.set_page_query(query); |
|
redis_cache |
|
.cache_results(&serde_json::to_string(&results)?, &url) |
|
.await?; |
|
return Ok(results); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
let mut results: SearchResults = match req.cookie("appCookie") { |
|
Some(cookie_value) => { |
|
let cookie_value: Cookie<'_> = |
|
serde_json::from_str(cookie_value.name_value().1)?; |
|
|
|
let engines: Vec<EngineHandler> = cookie_value |
|
.engines |
|
.iter() |
|
.filter_map(|name| EngineHandler::new(name)) |
|
.collect(); |
|
|
|
aggregate( |
|
query, |
|
page, |
|
config.aggregator.random_delay, |
|
config.debug, |
|
&engines, |
|
config.request_timeout, |
|
safe_search, |
|
) |
|
.await? |
|
} |
|
None => { |
|
aggregate( |
|
query, |
|
page, |
|
config.aggregator.random_delay, |
|
config.debug, |
|
&config.upstream_search_engines, |
|
config.request_timeout, |
|
safe_search, |
|
) |
|
.await? |
|
} |
|
}; |
|
if results.engine_errors_info().is_empty() && results.results().is_empty() { |
|
results.set_filtered(); |
|
} |
|
results.add_style(&config.style); |
|
redis_cache |
|
.cache_results(&serde_json::to_string(&results)?, &url) |
|
.await?; |
|
Ok(results) |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
fn is_match_from_filter_list( |
|
file_path: &str, |
|
query: &str, |
|
) -> Result<bool, Box<dyn std::error::Error>> { |
|
let mut flag = false; |
|
let mut reader = BufReader::new(File::open(file_path)?); |
|
for line in reader.by_ref().lines() { |
|
let re = Regex::new(&line?)?; |
|
if re.is_match(query) { |
|
flag = true; |
|
break; |
|
} |
|
} |
|
Ok(flag) |
|
} |
|
|
|
|
|
#[get("/robots.txt")] |
|
pub async fn robots_data(_req: HttpRequest) -> Result<HttpResponse, Box<dyn std::error::Error>> { |
|
let page_content: String = |
|
read_to_string(format!("{}/robots.txt", file_path(FileType::Theme)?))?; |
|
Ok(HttpResponse::Ok() |
|
.content_type("text/plain; charset=ascii") |
|
.body(page_content)) |
|
} |
|
|
|
|
|
#[get("/about")] |
|
pub async fn about( |
|
hbs: web::Data<Handlebars<'_>>, |
|
config: web::Data<Config>, |
|
) -> Result<HttpResponse, Box<dyn std::error::Error>> { |
|
let page_content: String = hbs.render("about", &config.style)?; |
|
Ok(HttpResponse::Ok().body(page_content)) |
|
} |
|
|
|
|
|
#[get("/settings")] |
|
pub async fn settings( |
|
hbs: web::Data<Handlebars<'_>>, |
|
config: web::Data<Config>, |
|
) -> Result<HttpResponse, Box<dyn std::error::Error>> { |
|
let page_content: String = hbs.render("settings", &config.style)?; |
|
Ok(HttpResponse::Ok().body(page_content)) |
|
} |
|
|