diff options
-rw-r--r-- | src/handlers.rs | 16 | ||||
-rw-r--r-- | src/main.rs | 1 | ||||
-rw-r--r-- | src/models.rs | 2 | ||||
-rw-r--r-- | src/render.rs | 21 | ||||
-rw-r--r-- | templates/index.html | 6 | ||||
-rw-r--r-- | templates/search.html | 48 |
6 files changed, 90 insertions, 4 deletions
diff --git a/src/handlers.rs b/src/handlers.rs index 33f33deccb87..02ff99394edf 100644 --- a/src/handlers.rs +++ b/src/handlers.rs @@ -176,6 +176,22 @@ pub fn reply_thread(state: State<AppState>, .responder() } +/// This handler executes a full-text search on the forum database and +/// displays the results to the user. +pub fn search_forum(state: State<AppState>, + query: Form<SearchPosts>) -> ConverseResponse { + let query_string = query.0.query.clone(); + state.db.send(query.0) + .flatten() + .and_then(move |results| state.renderer.send(SearchResultPage { + results, + query: query_string, + }).from_err()) + .flatten() + .map(|res| HttpResponse::Ok().content_type(HTML).body(res)) + .responder() +} + /// This handler initiates an OIDC login. pub fn login(state: State<AppState>) -> ConverseResponse { state.oidc.send(GetLoginUrl) diff --git a/src/main.rs b/src/main.rs index 9f4eea03fa31..92eeca48df64 100644 --- a/src/main.rs +++ b/src/main.rs @@ -153,6 +153,7 @@ fn main() { .resource("/thread/submit", |r| r.method(Method::POST).with3(submit_thread)) .resource("/thread/reply", |r| r.method(Method::POST).with3(reply_thread)) .resource("/thread/{id}", |r| r.method(Method::GET).with2(forum_thread)) + .resource("/search", |r| r.method(Method::POST).with2(search_forum)) .resource("/oidc/login", |r| r.method(Method::GET).with(login)) .resource("/oidc/callback", |r| r.method(Method::POST).with3(callback)); diff --git a/src/models.rs b/src/models.rs index 927a78513669..dfadd53fd34f 100644 --- a/src/models.rs +++ b/src/models.rs @@ -74,7 +74,7 @@ pub struct NewPost { /// This struct models the response of a full-text search query. It /// does not use a table/schema definition struct like the other /// tables, as no table of this type actually exists. -#[derive(QueryableByName, Debug)] +#[derive(QueryableByName, Debug, Serialize)] pub struct SearchResult { #[sql_type = "Integer"] pub post_id: i32, diff --git a/src/render.rs b/src/render.rs index 186b96d247c0..537cab59daef 100644 --- a/src/render.rs +++ b/src/render.rs @@ -175,3 +175,24 @@ impl Handler<NewThreadPage> for Renderer { Ok(self.tera.render("new-thread.html", &ctx)?) } } + +/// Message used to render search results +pub struct SearchResultPage { + pub query: String, + pub results: Vec<SearchResult>, +} + +impl Message for SearchResultPage { + type Result = Result<String>; +} + +impl Handler<SearchResultPage> for Renderer { + type Result = Result<String>; + + fn handle(&mut self, msg: SearchResultPage, _: &mut Self::Context) -> Self::Result { + let mut ctx = Context::new(); + ctx.add("query", &msg.query); + ctx.add("results", &msg.results); + Ok(self.tera.render("search.html", &ctx)?) + } +} diff --git a/templates/index.html b/templates/index.html index 35cfeba05023..263c8828a038 100644 --- a/templates/index.html +++ b/templates/index.html @@ -14,9 +14,9 @@ <a class="navbar-brand" href="/"> <h2>Converse</h2> </a> - <form class="form-inline"> - <input class="form-control mr-sm-2" type="search" placeholder="Search" aria-label="Search" disabled> - <button class="btn btn-outline-success my-2 my-sm-0 mr-1" type="submit" disabled>Search</button> + <form class="form-inline" method="post" action="/search"> + <input class="form-control mr-sm-2" type="search" placeholder="Search" name="query" aria-label="Search"> + <button class="btn btn-outline-success my-2 my-sm-0 mr-1" type="submit">Search</button> <a class="btn btn-outline-secondary my-2" href="/thread/new">New thread</a> </form> </nav> diff --git a/templates/search.html b/templates/search.html new file mode 100644 index 000000000000..95384b1caf58 --- /dev/null +++ b/templates/search.html @@ -0,0 +1,48 @@ +<!doctype html> +<html lang="en"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> + <meta http-equiv="Content-Security-Policy" content="script-src 'self';"> + <!-- Bootstrap CSS --> + <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous"> + <title>Converse Index</title> + </head> + <body> + <header> + <nav class="navbar navbar-light bg-light justify-content-between mb-3"> + <a class="navbar-brand" href="/"> + <h2>Converse</h2> + </a> + <form class="form-inline" method="post" action="/search"> + <input class="form-control mr-sm-2" type="search" placeholder="Search" name="query" aria-label="Search"> + <button class="btn btn-outline-success my-2 my-sm-0 mr-1" type="submit">Search</button> + <a class="btn btn-outline-secondary my-2" href="/thread/new">New thread</a> + <a class="btn btn-outline-secondary my-2" href="/">Back to index</a> + </form> + </nav> + </header> + <div class="container"> + <div class="row"> + <div class="col-4"> + <h2>Search results for '{{ query }}':</h2> + </div> + </div> + <div class="row"> + <div class="col-12"> + <div class="list-group"> + {% for result in results -%} + <a href="/thread/{{ result.thread_id }}#post-{{ result.post_id }}" class="list-group-item flex-column list-group-item-action align-items-start"> + <div class="d-flex w-100 justify-content-between"> + <h5 class="mb-1">In thread '{{ result.title }}':</h5> + <div>{{ result.headline }}</div> + <div>(Posted by <i>{{ result.author }})</i></div> + </div> + </a> + {%- endfor %} + </div> + </div> + </div> + </div> + </body> +</html> |