diff --git a/lib/PostText.pm b/lib/PostText.pm index dcbbce5..6ef3d79 100644 --- a/lib/PostText.pm +++ b/lib/PostText.pm @@ -146,6 +146,9 @@ sub startup($self) { $r->get('/feeds')->to('page#feeds')->name('feeds_page'); + # Not-so-static but I mean they're all 'pages' c'mon + $r->get('/search')->to('page#search')->name('search_page'); + $r->any([qw{GET POST}], '/captcha/*return_url') ->to('page#captcha') ->name('captcha_page'); diff --git a/lib/PostText/Controller/Page.pm b/lib/PostText/Controller/Page.pm index e8614bc..e5acc8b 100644 --- a/lib/PostText/Controller/Page.pm +++ b/lib/PostText/Controller/Page.pm @@ -50,4 +50,32 @@ sub captcha($self) { $self->render; } +sub search($self) { + my $v = $self->validation; + my $search_results = []; + my ($search_query, $this_page, $last_page, $base_path); + + if ($v->has_data) { + $v->required('q' )->size(1, 2_047); + $v->optional('page'); + + $search_query = $v->param('q'); + $this_page = $v->param('page') || 1; + $last_page = $self->page->last_page_for($search_query); + $base_path = $self->url_for->query(q => $search_query); + $search_results = $self->page->search($search_query, $this_page); + + $self->stash(status => 400) if $v->has_error; + } + + $self->stash( + this_page => $this_page, + last_page => $last_page, + base_path => $base_path, + search_results => $search_results + ); + + $self->render; +} + 1; diff --git a/lib/PostText/Model/Page.pm b/lib/PostText/Model/Page.pm index 4bf7e53..a4b5651 100644 --- a/lib/PostText/Model/Page.pm +++ b/lib/PostText/Model/Page.pm @@ -37,5 +37,28 @@ sub search($self, $search_query, $this_page = 1) { END_SQL } +sub count_for($self, $search_query) { + $self->pg->db->query(<<~'END_SQL', $search_query)->hash->{'post_tally'} + SELECT COUNT(*) AS post_tally + FROM (SELECT thread_date AS post_date + FROM threads + WHERE search_tokens @@ PLAINTO_TSQUERY('english', $1) + UNION ALL + SELECT remark_date + FROM remarks + WHERE search_tokens @@ PLAINTO_TSQUERY('english', $1)) + AS posts; + END_SQL +} + +sub last_page_for($self, $search_query) { + my $post_count = $self->count_for($search_query); + my $last_page = int($post_count / $self->per_page); + + # Add a page for 'remainder' posts + $last_page++ if $post_count % $self->per_page; + + return $last_page; +} 1; diff --git a/t/search.t b/t/search.t new file mode 100644 index 0000000..b0ff946 --- /dev/null +++ b/t/search.t @@ -0,0 +1,18 @@ +use Mojo::Base -strict; +use Test::More; +use Test::Mojo; + +my $t = Test::Mojo->new('PostText'); +my $invalid_query = 'aaaaaaaa' x 300; + +subtest Search => sub { + $t->get_ok('/search')->status_is(200)->text_like(h2 => qr/Search/); + + $t->get_ok('/search?q=test')->status_is(200) + ->text_like(h3 => qr/Results/); + + $t->get_ok("/search?q=$invalid_query")->status_is(400) + ->text_like(p => qr/Must be between/); +}; + +done_testing; diff --git a/templates/page/search.html.ep b/templates/page/search.html.ep new file mode 100644 index 0000000..0bea18f --- /dev/null +++ b/templates/page/search.html.ep @@ -0,0 +1,50 @@ +% layout 'default'; +% title 'Search Posts'; +

<%= title %>

+
+
+ <% if (my $error = validation->error('q')) { =%> +

Must be between <%= $error->[2] %> + and <%= $error->[3] %> characters.

+ <% } =%> + <%= label_for search => 'Search' %> + <%= text_field q => ( + id => 'search', + maxlength => 2047, + minlength => 1, + required => undef + ) %> +
+ +
+<% if (scalar @{$search_results}) { =%> +
+

Results

+ <% for my $result (@{$search_results}) { =%> +
+

+ + <%= $result->{'post_date'} %> + + <% if ($result->{'post_type'} eq 'thread') { =%> + <%= link_to "#$result->{'post_id'}", single_thread => + {thread_id => $result->{'post_id'}}, (class => 'post__id') %> + <% } else { =%> + <%= link_to "#$result->{'post_id'}", single_remark => + {remark_id => $result->{'post_id'}}, (class => 'post__id') %> + <% } =%> +

+ +
+ <%== markdown $result->{'post_body'} =%> +
+
+ <% } =%> + <% if ($last_page && $last_page != 1) { =%> + + <% } =%> +
+<% } =%>