rb/routing/pages/
search.rs

1use std::collections::HashMap;
2
3use reva_axum::Template;
4use axum::extract::Query;
5use axum::response::IntoResponse;
6use axum::{extract::State, response::Html};
7use axum_extra::extract::CookieJar;
8
9use authbeam::model::{Profile, RelationshipStatus};
10
11use super::{SearchHomeQuery, SearchQuery};
12use crate::config::Config;
13use crate::database::Database;
14use crate::model::{DatabaseError, FullResponse, Question};
15use crate::ToHtml;
16
17#[derive(Template)]
18#[template(path = "search/homepage.html")]
19struct HomepageTemplate {
20    config: Config,
21    lang: langbeam::LangFile,
22    profile: Option<Box<Profile>>,
23    unread: usize,
24    notifs: usize,
25    query: String,
26    driver: i8,
27}
28
29/// GET /search
30pub async fn search_homepage_request(
31    jar: CookieJar,
32    State(database): State<Database>,
33    Query(query): Query<SearchHomeQuery>,
34) -> impl IntoResponse {
35    let auth_user = match jar.get("__Secure-Token") {
36        Some(c) => match database
37            .auth
38            .get_profile_by_unhashed(c.value_trimmed())
39            .await
40        {
41            Ok(ua) => Some(ua),
42            Err(_) => None,
43        },
44        None => None,
45    };
46
47    let unread = if let Some(ref ua) = auth_user {
48        database.get_inbox_count_by_recipient(&ua.id).await
49    } else {
50        0
51    };
52
53    let notifs = if let Some(ref ua) = auth_user {
54        database
55            .auth
56            .get_notification_count_by_recipient(&ua.id)
57            .await
58    } else {
59        0
60    };
61
62    Html(
63        HomepageTemplate {
64            config: database.config.clone(),
65            lang: database.lang(if let Some(c) = jar.get("net.rainbeam.langs.choice") {
66                c.value_trimmed()
67            } else {
68                ""
69            }),
70            profile: auth_user.clone(),
71            unread,
72            notifs,
73            query: String::new(),
74            driver: query.driver,
75        }
76        .render()
77        .unwrap(),
78    )
79}
80
81#[derive(Template)]
82#[template(path = "search/responses.html")]
83struct ResponsesTemplate {
84    config: Config,
85    lang: langbeam::LangFile,
86    profile: Option<Box<Profile>>,
87    unread: usize,
88    notifs: usize,
89    query: String,
90    page: i32,
91    driver: i8,
92    // search-specific
93    results: Vec<FullResponse>,
94    relationships: HashMap<String, RelationshipStatus>,
95    is_powerful: bool, // at least "manager"
96    is_helper: bool,   // at least "helper"
97}
98
99/// GET /search/responses
100pub async fn search_responses_request(
101    jar: CookieJar,
102    State(database): State<Database>,
103    Query(query): Query<SearchQuery>,
104) -> impl IntoResponse {
105    let auth_user = match jar.get("__Secure-Token") {
106        Some(c) => match database
107            .auth
108            .get_profile_by_unhashed(c.value_trimmed())
109            .await
110        {
111            Ok(ua) => Some(ua),
112            Err(_) => None,
113        },
114        None => None,
115    };
116
117    let unread = if let Some(ref ua) = auth_user {
118        database.get_inbox_count_by_recipient(&ua.id).await
119    } else {
120        0
121    };
122
123    let notifs = if let Some(ref ua) = auth_user {
124        database
125            .auth
126            .get_notification_count_by_recipient(&ua.id)
127            .await
128    } else {
129        0
130    };
131
132    // search results
133    let results = if query.tag.is_empty() {
134        match database
135            .get_responses_searched_paginated(query.page, query.q.clone())
136            .await
137        {
138            Ok(responses) => responses,
139            Err(e) => return Html(e.to_html(database)),
140        }
141    } else {
142        match database
143            .get_responses_tagged_paginated(query.tag.clone(), query.page)
144            .await
145        {
146            Ok(responses) => responses,
147            Err(e) => return Html(e.to_html(database)),
148        }
149    };
150
151    // permissions
152    let mut is_helper: bool = false;
153    let is_powerful = if let Some(ref ua) = auth_user {
154        let group = match database.auth.get_group_by_id(ua.group).await {
155            Ok(g) => g,
156            Err(_) => return Html(DatabaseError::Other.to_html(database)),
157        };
158
159        is_helper = group.permissions.check_helper();
160        group.permissions.check_manager()
161    } else {
162        false
163    };
164
165    // build relationships list
166    let mut relationships: HashMap<String, RelationshipStatus> = HashMap::new();
167
168    if let Some(ref ua) = auth_user {
169        for response in &results {
170            if relationships.contains_key(&response.1.author.id) {
171                continue;
172            }
173
174            if response.1.author.id == ua.id {
175                // make sure we can view our own responses
176                relationships.insert(response.1.author.id.clone(), RelationshipStatus::Friends);
177                continue;
178            };
179
180            relationships.insert(
181                response.1.author.id.clone(),
182                database
183                    .auth
184                    .get_user_relationship(&response.1.author.id, &ua.id)
185                    .await
186                    .0,
187            );
188        }
189    } else {
190        for response in &results {
191            // no user, no relationships
192            if relationships.contains_key(&response.1.author.id) {
193                continue;
194            }
195
196            relationships.insert(response.1.author.id.clone(), RelationshipStatus::Unknown);
197        }
198    }
199
200    // render
201    Html(
202        ResponsesTemplate {
203            config: database.config.clone(),
204            lang: database.lang(if let Some(c) = jar.get("net.rainbeam.langs.choice") {
205                c.value_trimmed()
206            } else {
207                ""
208            }),
209            profile: auth_user.clone(),
210            unread,
211            notifs,
212            query: query.q,
213            page: query.page,
214            driver: if query.tag.is_empty() { 0 } else { 4 },
215            // search-specific
216            results,
217            relationships,
218            is_powerful,
219            is_helper,
220        }
221        .render()
222        .unwrap(),
223    )
224}
225
226#[derive(Template)]
227#[template(path = "search/questions.html")]
228struct QuestionsTemplate {
229    config: Config,
230    lang: langbeam::LangFile,
231    profile: Option<Box<Profile>>,
232    unread: usize,
233    notifs: usize,
234    query: String,
235    page: i32,
236    driver: i8,
237    // search-specific
238    results: Vec<(Question, usize, usize)>,
239    relationships: HashMap<String, RelationshipStatus>,
240    is_helper: bool, // at least "helper"
241}
242
243/// GET /search/questions
244pub async fn search_questions_request(
245    jar: CookieJar,
246    State(database): State<Database>,
247    Query(query): Query<SearchQuery>,
248) -> impl IntoResponse {
249    let auth_user = match jar.get("__Secure-Token") {
250        Some(c) => match database
251            .auth
252            .get_profile_by_unhashed(c.value_trimmed())
253            .await
254        {
255            Ok(ua) => Some(ua),
256            Err(_) => None,
257        },
258        None => None,
259    };
260
261    let unread = if let Some(ref ua) = auth_user {
262        database.get_inbox_count_by_recipient(&ua.id).await
263    } else {
264        0
265    };
266
267    let notifs = if let Some(ref ua) = auth_user {
268        database
269            .auth
270            .get_notification_count_by_recipient(&ua.id)
271            .await
272    } else {
273        0
274    };
275
276    // search results
277    let results = match database
278        .get_global_questions_searched_paginated(query.page, query.q.clone())
279        .await
280    {
281        Ok(responses) => responses,
282        Err(e) => return Html(e.to_html(database)),
283    };
284
285    // build relationships list
286    let mut relationships: HashMap<String, RelationshipStatus> = HashMap::new();
287
288    if let Some(ref ua) = auth_user {
289        for question in &results {
290            if relationships.contains_key(&question.0.author.id) {
291                continue;
292            }
293
294            if question.0.author.id == ua.id {
295                // make sure we can view our own questions
296                relationships.insert(question.0.author.id.clone(), RelationshipStatus::Friends);
297                continue;
298            };
299
300            relationships.insert(
301                question.0.author.id.clone(),
302                database
303                    .auth
304                    .get_user_relationship(&question.0.author.id, &ua.id)
305                    .await
306                    .0,
307            );
308        }
309    } else {
310        for question in &results {
311            // no user, no relationships
312            if relationships.contains_key(&question.0.author.id) {
313                continue;
314            }
315
316            relationships.insert(question.0.author.id.clone(), RelationshipStatus::Unknown);
317        }
318    }
319
320    // permissions
321    let is_helper = if let Some(ref ua) = auth_user {
322        let group = match database.auth.get_group_by_id(ua.group).await {
323            Ok(g) => g,
324            Err(_) => return Html(DatabaseError::Other.to_html(database)),
325        };
326
327        group.permissions.check_helper()
328    } else {
329        false
330    };
331
332    // render
333    Html(
334        QuestionsTemplate {
335            config: database.config.clone(),
336            lang: database.lang(if let Some(c) = jar.get("net.rainbeam.langs.choice") {
337                c.value_trimmed()
338            } else {
339                ""
340            }),
341            profile: auth_user.clone(),
342            unread,
343            notifs,
344            query: query.q,
345            page: query.page,
346            driver: 1,
347            // search-specific
348            results,
349            relationships,
350            is_helper,
351        }
352        .render()
353        .unwrap(),
354    )
355}
356
357#[derive(Template)]
358#[template(path = "search/users.html")]
359struct UsersTemplate {
360    config: Config,
361    lang: langbeam::LangFile,
362    profile: Option<Box<Profile>>,
363    unread: usize,
364    notifs: usize,
365    query: String,
366    page: i32,
367    driver: i8,
368    // search-specific
369    results: Vec<Box<Profile>>,
370}
371
372/// GET /search/users
373pub async fn search_users_request(
374    jar: CookieJar,
375    State(database): State<Database>,
376    Query(query): Query<SearchQuery>,
377) -> impl IntoResponse {
378    let auth_user = match jar.get("__Secure-Token") {
379        Some(c) => match database
380            .auth
381            .get_profile_by_unhashed(c.value_trimmed())
382            .await
383        {
384            Ok(ua) => Some(ua),
385            Err(_) => None,
386        },
387        None => None,
388    };
389
390    let unread = if let Some(ref ua) = auth_user {
391        database.get_inbox_count_by_recipient(&ua.id).await
392    } else {
393        0
394    };
395
396    let notifs = if let Some(ref ua) = auth_user {
397        database
398            .auth
399            .get_notification_count_by_recipient(&ua.id)
400            .await
401    } else {
402        0
403    };
404
405    // search results
406    let results = match database
407        .get_profiles_searched_paginated(query.page, query.q.clone())
408        .await
409    {
410        Ok(responses) => responses,
411        Err(e) => return Html(e.to_html(database)),
412    };
413
414    // render
415    Html(
416        UsersTemplate {
417            config: database.config.clone(),
418            lang: database.lang(if let Some(c) = jar.get("net.rainbeam.langs.choice") {
419                c.value_trimmed()
420            } else {
421                ""
422            }),
423            profile: auth_user.clone(),
424            unread,
425            notifs,
426            query: query.q,
427            page: query.page,
428            driver: 3,
429            // search-specific
430            results,
431        }
432        .render()
433        .unwrap(),
434    )
435}