caracal 0.2.0

Nostr client for Gemini
use crate::aska::WindTemplate;
use crate::rendering;
use crate::routes::response::*;
use crate::routes::{gem, t, ugate};
use askama::Template;
use mdiu::Block;
use nostr_sdk::prelude::*;
use windmark_titanesque::{context::RouteContext, response::Response};

const NOTES_PER_PAGE: usize = 128;

#[derive(Template)]
#[template(path = "search_notes.gmi", escape = "txt")]
pub struct NotesSearchTemplate {
    results_count: usize,
    page_cur: usize,
    page_last: usize,
    matching_notes: Vec<Block>,
}

impl NotesSearchTemplate {
    fn display_results_count(&self) -> String {
        t!(
            "search_results_summary",
            count = self.results_count,
            current_page = self.page_cur + 1,
            last_page = self.page_last + 1
        )
        .to_string()
    }
}

/// Route to run a search query on text notes
pub async fn search_content(ctx: RouteContext) -> Response {
    let Some(user) = ugate(ctx.certificate).await else {
        return resp_nocert();
    };

    /* If the "searchq" path parameter isn't found, ask the user for
     * a search query via an input request and then redirect to a URL
     * containing the query in the path */
    let Some(searchq) = ctx.parameters.get("searchq") else {
        if let Some(q) = dec_urlq(&ctx.url) {
            return Response::temporary_redirect(format!(
                "/search/notes/{}/0",
                q
            ));
        } else {
            return Response::input(t!("search_query_input"));
        }
    };

    let page = match ctx
        .parameters
        .get("page")
        .unwrap_or(&String::from("0"))
        .parse::<usize>()
    {
        Ok(num) => num,
        Err(_error) => 0,
    };

    let sfilter = Filter::new().kind(Kind::TextNote).search(searchq);

    if let Ok(events) = user.client.database().query(sfilter).await {
        let results_count = events.len();

        if results_count == 0 {
            return resp_noresults_for_query();
        }

        let page_max = events.len() / NOTES_PER_PAGE;
        let start = page * NOTES_PER_PAGE;
        let end = start + NOTES_PER_PAGE;

        match rendering::render_notes(
            events,
            &user.client,
            Some(start),
            Some(end),
            rendering::NoteRenderMode::Normal,
        )
        .await
        {
            Ok(blocks) => {
                Response::success(WindTemplate::render(
                    NotesSearchTemplate {
                        results_count,
                        page_cur: page,
                        page_last: page_max,
                        matching_notes: blocks,
                    },
                ))
            }
            Err(_err) => {
                Response::temporary_failure("Rendering error")
            }
        }
    } else {
        resp_db_query_error()
    }
}