feat: display search results

This commit is contained in:
Aravinth Manivannan 2023-03-04 18:59:10 +05:30
parent 6cc56919e3
commit cd797fba83
Signed by: realaravinth
GPG key ID: AD9F0F08E855ED88
7 changed files with 168 additions and 28 deletions

View file

@ -32,15 +32,17 @@ use crate::*;
pub use crate::pages::*; pub use crate::pages::*;
pub const TITLE: &str = "Explore"; pub const TITLE: &str = "Explore";
pub const HOME: TemplateFile = TemplateFile::new("home_page", "pages/chart/index.html"); pub const EXPLORE: TemplateFile = TemplateFile::new("explore_page", "pages/chart/index.html");
pub const REPO_INFO: TemplateFile = pub const REPO_INFO: TemplateFile =
TemplateFile::new("repo_info", "pages/chart/components/repo_info.html"); TemplateFile::new("repo_info", "pages/chart/components/repo_info.html");
pub struct HomePage { pub const SEARCH_BAR: TemplateFile = TemplateFile::new("search_bar", "components/nav/search.html");
pub struct ExplorePage {
ctx: RefCell<Context>, ctx: RefCell<Context>,
} }
impl CtxError for HomePage { impl CtxError for ExplorePage {
fn with_error(&self, e: &ReadableError) -> String { fn with_error(&self, e: &ReadableError) -> String {
self.ctx.borrow_mut().insert(ERROR_KEY, e); self.ctx.borrow_mut().insert(ERROR_KEY, e);
self.render() self.render()
@ -48,14 +50,14 @@ impl CtxError for HomePage {
} }
#[derive(Clone, Debug, PartialEq, Eq, Default, Deserialize, Serialize)] #[derive(Clone, Debug, PartialEq, Eq, Default, Deserialize, Serialize)]
pub struct HomePagePayload { pub struct ExplorePagePayload {
pub repos: Vec<Repository>, pub repos: Vec<Repository>,
pub next_page: String, pub next_page: String,
pub prev_page: String, pub prev_page: String,
} }
impl HomePage { impl ExplorePage {
fn new(settings: &Settings, payload: &HomePagePayload) -> Self { fn new(settings: &Settings, payload: &ExplorePagePayload) -> Self {
let ctx = RefCell::new(ctx(settings)); let ctx = RefCell::new(ctx(settings));
ctx.borrow_mut().insert(TITLE_KEY, TITLE); ctx.borrow_mut().insert(TITLE_KEY, TITLE);
ctx.borrow_mut().insert(PAYLOAD_KEY, payload); ctx.borrow_mut().insert(PAYLOAD_KEY, payload);
@ -63,17 +65,17 @@ impl HomePage {
} }
pub fn render(&self) -> String { pub fn render(&self) -> String {
TEMPLATES.render(HOME.name, &self.ctx.borrow()).unwrap() TEMPLATES.render(EXPLORE.name, &self.ctx.borrow()).unwrap()
} }
pub fn page(s: &Settings, payload: &HomePagePayload) -> String { pub fn page(s: &Settings, payload: &ExplorePagePayload) -> String {
let p = Self::new(s, payload); let p = Self::new(s, payload);
p.render() p.render()
} }
} }
pub fn services(cfg: &mut web::ServiceConfig) { pub fn services(cfg: &mut web::ServiceConfig) {
cfg.service(home); cfg.service(explore);
} }
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
@ -109,14 +111,18 @@ impl From<OptionalPage> for Page {
} }
} }
#[get(path = "PAGES.home")] #[get(path = "PAGES.explore")]
pub async fn home( pub async fn explore(
q: web::Query<OptionalPage>, q: web::Query<OptionalPage>,
ctx: WebCtx, ctx: WebCtx,
db: WebDB, db: WebDB,
) -> PageResult<impl Responder, HomePage> { ) -> PageResult<impl Responder, ExplorePage> {
let q = q.into_inner(); let q = q.into_inner();
async fn _home(_ctx: &ArcCtx, db: &BoxDB, p: &Page) -> ServiceResult<Vec<db_core::Repository>> { async fn _explore(
_ctx: &ArcCtx,
db: &BoxDB,
p: &Page,
) -> ServiceResult<Vec<db_core::Repository>> {
const LIMIT: u32 = 10; const LIMIT: u32 = 10;
let offset = p.page * LIMIT; let offset = p.page * LIMIT;
let responses = db.get_all_repositories(offset, LIMIT).await?; let responses = db.get_all_repositories(offset, LIMIT).await?;
@ -124,17 +130,17 @@ pub async fn home(
} }
let q: Page = q.into(); let q: Page = q.into();
let repos = _home(&ctx, &db, &q).await.map_err(|e| { let repos = _explore(&ctx, &db, &q).await.map_err(|e| {
let x = HomePagePayload::default(); let x = ExplorePagePayload::default();
PageError::new(HomePage::new(&ctx.settings, &x), e) PageError::new(ExplorePage::new(&ctx.settings, &x), e)
})?; })?;
let payload = HomePagePayload { let payload = ExplorePagePayload {
repos, repos,
next_page: PAGES.home_next(q.next()), next_page: PAGES.explore_next(q.next()),
prev_page: PAGES.home_next(q.prev()), prev_page: PAGES.explore_next(q.prev()),
}; };
let page = HomePage::page(&ctx.settings, &payload); let page = ExplorePage::page(&ctx.settings, &payload);
let html = ContentType::html(); let html = ContentType::html();
Ok(HttpResponse::Ok().content_type(html).body(page)) Ok(HttpResponse::Ok().content_type(html).body(page))

View file

@ -17,16 +17,22 @@
*/ */
pub mod home; pub mod home;
pub use home::HOME; pub mod search;
pub use home::EXPLORE;
pub use home::REPO_INFO; pub use home::REPO_INFO;
pub use home::SEARCH_BAR;
pub use search::SEARCH_RESULTS;
pub use super::{ctx, TemplateFile, ERROR_KEY, PAGES, PAYLOAD_KEY, TITLE_KEY}; pub use super::{ctx, TemplateFile, ERROR_KEY, PAGES, PAYLOAD_KEY, TITLE_KEY};
pub fn register_templates(t: &mut tera::Tera) { pub fn register_templates(t: &mut tera::Tera) {
HOME.register(t).expect(HOME.name); EXPLORE.register(t).expect(EXPLORE.name);
REPO_INFO.register(t).expect(REPO_INFO.name); REPO_INFO.register(t).expect(REPO_INFO.name);
SEARCH_BAR.register(t).expect(SEARCH_BAR.name);
SEARCH_RESULTS.register(t).expect(SEARCH_RESULTS.name);
} }
pub fn services(cfg: &mut actix_web::web::ServiceConfig) { pub fn services(cfg: &mut actix_web::web::ServiceConfig) {
home::services(cfg); home::services(cfg);
search::services(cfg);
} }

109
src/pages/chart/search.rs Normal file
View file

@ -0,0 +1,109 @@
/*
* ForgeFlux StarChart - A federated software forge spider
* Copyright © 2022 Aravinth Manivannan <realaravinth@batsense.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
use actix_web::http::header::ContentType;
use actix_web::{HttpResponse, Responder};
use actix_web_codegen_const_routes::post;
use serde::{Deserialize, Serialize};
use std::cell::RefCell;
use tera::Context;
use db_core::prelude::*;
use crate::errors::ServiceResult;
use crate::pages::errors::*;
use crate::settings::Settings;
use crate::*;
pub use crate::pages::*;
pub const TITLE: &str = "Search";
pub const SEARCH_QUERY_KEY: &str = "search_query";
pub const SEARCH_RESULTS: TemplateFile =
TemplateFile::new("search_results", "pages/chart/search.html");
pub struct SearchPage {
ctx: RefCell<Context>,
}
impl CtxError for SearchPage {
fn with_error(&self, e: &ReadableError) -> String {
self.ctx.borrow_mut().insert(ERROR_KEY, e);
self.render()
}
}
#[derive(Clone, Debug, PartialEq, Eq, Default, Deserialize, Serialize)]
pub struct SearchPagePayload {
pub repos: Vec<Repository>,
}
impl SearchPage {
fn new(settings: &Settings, payload: &SearchPagePayload, search_query: Option<&str>) -> Self {
let ctx = RefCell::new(ctx(settings));
ctx.borrow_mut().insert(TITLE_KEY, TITLE);
ctx.borrow_mut().insert(PAYLOAD_KEY, payload);
if let Some(search_query) = search_query {
ctx.borrow_mut().insert(SEARCH_QUERY_KEY, search_query);
}
Self { ctx }
}
pub fn render(&self) -> String {
TEMPLATES
.render(SEARCH_RESULTS.name, &self.ctx.borrow())
.unwrap()
}
pub fn page(s: &Settings, payload: &SearchPagePayload, search_query: Option<&str>) -> String {
let p = Self::new(s, payload, search_query);
p.render()
}
}
pub fn services(cfg: &mut web::ServiceConfig) {
cfg.service(search);
}
#[post(path = "PAGES.search")]
pub async fn search(
payload: web::Form<crate::search::SearchRepositoryReq>,
ctx: WebCtx,
db: WebDB,
) -> PageResult<impl Responder, SearchPage> {
async fn _search(
ctx: &ArcCtx,
db: &BoxDB,
query: String,
) -> ServiceResult<Vec<db_core::Repository>> {
let responses = ctx.search_repository(&db, query).await?;
Ok(responses)
}
let query = payload.into_inner().query;
let repos = _search(&ctx, &db, query.clone()).await.map_err(|e| {
let x = SearchPagePayload::default();
PageError::new(SearchPage::new(&ctx.settings, &x, Some(&query)), e)
})?;
let payload = SearchPagePayload { repos };
let page = SearchPage::page(&ctx.settings, &payload, Some(&query));
let html = ContentType::html();
Ok(HttpResponse::Ok().content_type(html).body(page))
}

View file

@ -143,7 +143,7 @@ mod tests {
PUB_NAV, PUB_NAV,
auth::AUTH_CHALLENGE, auth::AUTH_CHALLENGE,
auth::AUTH_ADD, auth::AUTH_ADD,
chart::HOME, chart::EXPLORE,
// auth::AUTH_BASE, // auth::AUTH_BASE,
// auth::login::LOGIN, // auth::login::LOGIN,
// auth::register::REGISTER, // auth::register::REGISTER,

View file

@ -25,6 +25,8 @@ pub const PAGES: Pages = Pages::new();
pub struct Pages { pub struct Pages {
/// home page /// home page
pub home: &'static str, pub home: &'static str,
pub explore: &'static str,
pub search: &'static str,
/// auth routes /// auth routes
pub auth: Auth, pub auth: Auth,
} }
@ -32,13 +34,20 @@ pub struct Pages {
impl Pages { impl Pages {
/// create new instance of Routes /// create new instance of Routes
const fn new() -> Pages { const fn new() -> Pages {
let home = "/"; let explore = "/explore";
let home = explore;
let search = "/search";
let auth = Auth::new(); let auth = Auth::new();
Pages { home, auth } Pages {
home,
auth,
explore,
search,
}
} }
pub fn home_next(&self, page: u32) -> String { pub fn explore_next(&self, page: u32) -> String {
format!("{}?page={page}", self.home) format!("{}?page={page}", self.explore)
} }
} }

View file

@ -1,4 +1,3 @@
{% extends 'base' %} {% extends 'base' %}
{% block title %} {{ title }} {% endblock %} {% block title %} {{ title }} {% endblock %}
{% block nav %} {% include "pub_nav" %} {% endblock %} {% block nav %} {% include "pub_nav" %} {% endblock %}

View file

@ -0,0 +1,11 @@
{% extends 'base' %}
{% block title %} {{ title }} {% endblock %}
{% block nav %} {% include "pub_nav" %} {% endblock %}
{% block main %}
<section class="main">
{% for repository in payload.repos %}
{% include "repo_info" %}
{% endfor %}
</section>
{% endblock %}