File size: 8,432 Bytes
fc69ace
1f90b4e
fc69ace
 
15fc415
 
c170de8
 
4402168
5aca5c0
b170574
4402168
c170de8
15fc415
 
 
a596c07
15fc415
fc69ace
 
 
 
 
 
 
 
8133de1
15fc415
 
 
 
 
fc69ace
15fc415
 
 
137c62e
15fc415
137c62e
15fc415
 
 
fc69ace
 
15fc415
 
137c62e
15fc415
137c62e
15fc415
 
 
 
 
 
94ef62e
 
 
 
 
 
 
3e6f786
 
 
 
 
 
 
 
fc69ace
 
 
 
 
 
 
 
137c62e
fc69ace
 
 
 
 
15fc415
 
 
 
137c62e
15fc415
 
 
 
 
4402168
15fc415
4402168
15fc415
4402168
 
a596c07
4402168
 
a596c07
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4402168
a596c07
 
4402168
15fc415
 
 
 
 
 
 
4402168
 
99936bc
4402168
 
74e4fc6
4402168
74e4fc6
4402168
 
 
 
99936bc
c5fca32
4402168
 
987e667
4402168
74e4fc6
 
 
 
987e667
74e4fc6
 
 
 
5aca5c0
 
 
 
 
 
 
74e4fc6
 
 
 
 
5aca5c0
2d47e8d
74e4fc6
 
 
 
 
 
 
 
 
 
2d47e8d
74e4fc6
 
 
 
987e667
 
 
4402168
 
 
 
fc69ace
15fc415
 
9c71c9f
 
15fc415
 
 
 
 
fc69ace
15fc415
 
 
137c62e
15fc415
137c62e
15fc415
 
 
fc69ace
15fc415
 
 
137c62e
15fc415
137c62e
15fc415
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
//! This module provides the functionality to handle different routes of the `websurfx`
//! meta search engine website and provide appropriate response to each route/page
//! when requested.

use std::fs::read_to_string;

use crate::{
    cache::cacher::RedisCache,
    config::parser::Config,
    engines::engine_models::EngineHandler,
    handler::paths::{file_path, FileType},
    results::{aggregation_models::SearchResults, aggregator::aggregate},
};
use actix_web::{get, web, HttpRequest, HttpResponse};
use handlebars::Handlebars;
use serde::Deserialize;
use tokio::join;

/// A named struct which deserializes all the user provided search parameters and stores them.
///
/// # Fields
///
/// * `q` - It stores the search parameter option `q` (or query in simple words)
/// of the search url.
/// * `page` - It stores the search parameter `page` (or pageno in simple words)
/// of the search url.
#[derive(Deserialize)]
struct SearchParams {
    q: Option<String>,
    page: Option<u32>,
}

/// Handles the route of index page or main page of the `websurfx` meta search engine website.
#[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))
}

/// 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<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))
}

/// A named struct which is used to deserialize the cookies fetched from the client side.
///
/// # Fields
///
/// * `theme` - It stores the theme name used in the website.
/// * `colorscheme` - It stores the colorscheme name used for the website theme.
/// * `engines` - It stores the user selected upstream search engines selected from the UI.
#[allow(dead_code)]
#[derive(Deserialize)]
struct Cookie {
    theme: String,
    colorscheme: String,
    engines: Vec<String>,
}

/// 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.
///
/// # Example
///
/// ```bash
/// curl "http://127.0.0.1:8080/search?q=sweden&page=1"
/// ```
///
/// Or
///
/// ```bash
/// curl "http://127.0.0.1:8080/search?q=sweden"
/// ```
#[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 &params.q {
        Some(query) => {
            if query.trim().is_empty() {
                return Ok(HttpResponse::Found()
                    .insert_header(("location", "/"))
                    .finish());
            }
            let page = match &params.page {
                Some(page) => *page,
                None => 1,
            };

            let (_, results, _) = join!(
                results(
                    format!(
                        "http://{}:{}/search?q={}&page={}",
                        config.binding_ip,
                        config.port,
                        query,
                        page - 1
                    ),
                    &config,
                    query.to_string(),
                    page - 1,
                    req.clone(),
                ),
                results(
                    format!(
                        "http://{}:{}/search?q={}&page={}",
                        config.binding_ip, config.port, query, page
                    ),
                    &config,
                    query.to_string(),
                    page,
                    req.clone(),
                ),
                results(
                    format!(
                        "http://{}:{}/search?q={}&page={}",
                        config.binding_ip,
                        config.port,
                        query,
                        page + 1
                    ),
                    &config,
                    query.to_string(),
                    page + 1,
                    req.clone(),
                )
            );

            let page_content: String = hbs.render("search", &results?)?;
            Ok(HttpResponse::Ok().body(page_content))
        }
        None => Ok(HttpResponse::Found()
            .insert_header(("location", "/"))
            .finish()),
    }
}

/// Fetches the results for a query and page.
/// First checks the redis cache, if that fails it gets proper results
async fn results(
    url: String,
    config: &Config,
    query: String,
    page: u32,
    req: HttpRequest,
) -> Result<SearchResults, Box<dyn std::error::Error>> {
    //Initialize redis cache connection struct
    let mut redis_cache = RedisCache::new(config.redis_url.clone())?;
    // fetch the cached results json.
    let cached_results_json = redis_cache.cached_json(&url);
    // check if fetched cache results was indeed fetched or it was an error and if so
    // handle the data accordingly.
    match cached_results_json {
        Ok(results) => Ok(serde_json::from_str::<SearchResults>(&results).unwrap()),
        Err(_) => {
            // check if the cookie value is empty or not if it is empty then use the
            // default selected upstream search engines from the config file otherwise
            // parse the non-empty cookie and grab the user selected engines from the
            // UI and use that.
            let mut results: crate::results::aggregation_models::SearchResults = match req
                .cookie("appCookie")
            {
                Some(cookie_value) => {
                    let cookie_value: Cookie = serde_json::from_str(cookie_value.name_value().1)?;

                    let engines = cookie_value
                        .engines
                        .iter()
                        .filter_map(|name| EngineHandler::new(name))
                        .collect();

                    aggregate(
                        query,
                        page,
                        config.aggregator.random_delay,
                        config.debug,
                        engines,
                        config.request_timeout,
                    )
                    .await?
                }
                None => {
                    aggregate(
                        query,
                        page,
                        config.aggregator.random_delay,
                        config.debug,
                        config.upstream_search_engines.clone(),
                        config.request_timeout,
                    )
                    .await?
                }
            };
            results.add_style(config.style.clone());
            redis_cache.cache_results(serde_json::to_string(&results)?, &url)?;
            Ok(results)
        }
    }
}

/// Handles the route of robots.txt page of the `websurfx` meta search engine website.
#[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))
}

/// Handles the route of about page of the `websurfx` meta search engine website.
#[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))
}

/// Handles the route of settings page of the `websurfx` meta search engine website.
#[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))
}