Compare commits

...

11 Commits

Author SHA1 Message Date
barnold a7b8116ff7 Add a comment about spawn-fcgi and lighttpd. 2022-10-02 13:05:47 +01:00
barnold dd6ea0a0ba Improve links to be "portable"...
so that they still work if the app is server under a 'root' path like
'/pg-catalog/...' instead of directly under '/...'.
2022-10-02 12:49:09 +01:00
barnold 595b4be156 Reduce vertical space taken by page navigation. 2022-09-29 18:04:47 +01:00
barnold 906055e410 Show more rows by default. Smaller headings. 2022-09-29 15:11:41 +01:00
barnold 85dde076e4 Simplify navigation...
Put all the links at the top, unconditionally.  Make the search forms
look slightly nicer.
2022-09-29 14:14:43 +01:00
barnold 76df89a970 Login, logout all gone. 2022-09-29 13:22:21 +01:00
barnold 24fbcf708b Put the about-page under the book controller. 2022-09-29 13:18:03 +01:00
barnold 4c5626c9ef Get tests passing again. 2022-09-29 13:10:43 +01:00
barnold 4c6d5bf321 Start getting rid of login-logout. Home page shows books. 2022-09-29 12:37:03 +01:00
barnold 501736f901 Change handling of beyond-last-page...
Redirect to the last page instead of to 404.
2022-09-29 11:15:40 +01:00
barnold 5cc21a2e7a Take the rpp preference out of auth...
and put it in the layout. The form response now redirects back to the current
page. Catch: if on the last page and you increase rpp, it redirects beyond
the last page and therefore 404s.
2022-09-29 10:59:37 +01:00
17 changed files with 142 additions and 168 deletions

View File

@ -82,30 +82,12 @@ sub startup ($self) {
# Set routes.
my $r = $self->routes;
# Home gets an explicit name, else its name is an empty string. A
# route's default name seems to come from '/path' with the leading
# '/' removed.
$r->get('/')->to('home#index')->name('home');
$r->get('/about')->to('home#about');
$r->get('/auth')->to('auth#index');
$r->post('/login')->to('auth#login');
$r->get('/logout')->to('auth#logout');
$r->get('/books/<page_number:num>')->to('book#books')->name('books');
$r->get('/authors/<page_number:num>')->to('book#authors')->name('authors');
$r->get('/<page_number:num>')->to('book#books', page_number => 1)->name('books');
$r->get('/about')->to('book#about')->name('about');
$r->get('/authors/<page_number:num>')
->to('book#authors', page_number => 1)->name('authors');
$r->get('/author/<id:num>/<page_number:num>')->to('book#author')->name('author');
# Put a route under athentication.
my $auth = $r->under(
'/' => sub ($c) {
return 1 if ($c->logname);
$c->flash('naughty' => 1);
$c->session('post_login' => $c->current_route);
$c->redirect_to('auth');
return undef;
}
);
$auth->get('/account')->to('auth#account')->name('account');
$auth->post('/preferences')->to('auth#preferences')->name('preferences');
$r->post('/set_rpp')->to('book#set_rpp')->name('set_rpp');
}
1;

View File

@ -1,44 +0,0 @@
package MyApp::Controller::Auth;
use Mojo::Base 'Mojolicious::Controller', -signatures;
use List::Util qw( min max );
sub index ($self) {
my $msg = $self->flash('err');
$self->render(name_msg => $msg);
}
sub login ($self) {
my $logname = $self->param('logname');
if ($logname =~ m/^[[:alpha:]]+$/) {
$self->session(logname => $logname);
my $post_login = $self->session('post_login');
$self->session('post_login' => undef);
$self->redirect_to($post_login || 'home');
} else {
$self->flash(err => "Try again!");
$self->flash(attempted_name => $logname);
$self->redirect_to('auth');
}
}
sub logout ($self) {
# "Delete whole session by setting an expiration date in the past"
# - from Mojolicious::Controller.
$self->session(expiration => 1);
$self->redirect_to('home');
}
sub preferences ($self) {
my $rpp = $self->accept_posint($self->param('rows_per_page'));
if ((!defined $rpp) || $rpp < 1 || $rpp > $self->max_rpp) {
$self->redirect_to('not_found');
return
}
$self->session(rows_per_page => $rpp);
$self->flash(
confirmation => sprintf("Updated rows per page to %s.", $self->rpp)
);
$self->redirect_to('account');
}
1;

View File

@ -17,7 +17,9 @@ sub books ($self) {
);
my $pager = $book_page->pager;
if ($page_number > $pager->last_page) {
$self->redirect_to('not_found');
$self->redirect_to(
$self->url_with({ page_number => $pager->last_page })
);
return;
}
$self->render(
@ -41,7 +43,9 @@ sub authors ($self) {
);
my $pager = $author_page->pager;
if ($page_number > $pager->last_page) {
$self->redirect_to('not_found');
$self->redirect_to(
$self->url_with({ page_number => $pager->last_page })
);
return;
}
$self->render(
@ -67,7 +71,9 @@ sub author ($self) {
}
my $pager = $book_page->pager;
if ($page_number > $pager->last_page) {
$self->redirect_to('not_found');
$self->redirect_to(
$self->url_with({ page_number => $pager->last_page })
);
return;
}
$self->render(
@ -77,4 +83,17 @@ sub author ($self) {
);
}
sub set_rpp ($self) {
my $rpp = $self->accept_posint($self->param('rows_per_page'));
if ((!defined $rpp) || $rpp < 1 || $rpp > $self->max_rpp) {
$self->redirect_to('not_found');
return
}
$self->session(rows_per_page => $rpp);
$self->flash(
confirmation => sprintf("Updated rows per page to %s.", $self->rpp)
);
$self->redirect_to($self->param('target_url'));
}
1;

View File

@ -1,8 +0,0 @@
package MyApp::Controller::Home;
use Mojo::Base 'Mojolicious::Controller', -signatures;
# No need for a method for either 'index' or 'about'.
# sub index ($self) {
# }
1;

View File

@ -1,5 +1,5 @@
---
secrets:
- 197b9b0060f3285c0909d83598e54f9ec0602151
default-rows-per-page: 10
default-rows-per-page: 12
maximum-rows-per-page: 100

View File

@ -1,3 +1,34 @@
h1 {
font-size: large;
}
h2 {
font-size: medium;
}
form > span {
margin-right: 1em;
}
ul.top {
margin: 0;
padding-left: 0;
}
ul.top > li {
display: inline-block;
padding-right: 1em;
min-width: 8ch;
}
div.page-nav {
margin-top: 1em;
}
div.page-nav > span {
padding-right: 1em;
}
div.verbiage {
max-width: 40em;
}

View File

@ -1,4 +1,16 @@
#!/usr/bin/env perl
#
# A note about lighttpd and fastcgi, based on
# <https://github.com/mojolicious/mojo/wiki/Deploying-on-Lighttpd-with-FastCGI>
# which shows how to start the app via lighttpd. However, it doesn't
# show how to start the app yourself, outside of lighttpd, in such
# a way that it'll work under fcgi.
#
# To do this:
# Change the lighttpd conf to remove "bin-path" and instead use (e.g.)
# "host" => "localhost".
# Then to start the app, use 'spawn-fcgi', e.g.
# spawn-fcgi -a 127.0.0.1 -p 8080 -- script/my_app fastcgi
use strict;
use warnings;

View File

@ -1,20 +1,13 @@
use Mojo::Base -strict;
use Test2::V0;
use Test2::Plugin::BailOnFail;
use Test::Mojo;
my $t = Test::Mojo->new('MyApp');
$t->get_ok('/')->status_is(200)->content_like(qr/home/i);
$t->get_ok('/')->status_is(200)->content_like(qr/Books/);
$t->get_ok('/about')->status_is(200)->content_like(qr/about/i);
# No way to follow the redirect?
$t->get_ok('/account')->status_is(302);
$t->get_ok('/auth')->status_is(200)->content_like(qr/login/i);
$t->post_ok('/login' => form => { logname => 'Dobby' });
$t->get_ok('/account')->status_is(200)->content_like(qr/dobby/i);
$t->get_ok('/books/1')->status_is(200)->content_like(qr/[[:digit:]] books/i);
$t->get_ok('/1')->status_is(200)->content_like(qr/[[:digit:]] books/i);
$t->get_ok('/authors/1')->status_is(200)->content_like(qr/[[:digit:]] authors/i);
$t->get_ok('/logout')->status_is(302);
# Can't do "content_unlike()" so check it offers login now.
$t->get_ok('/')->status_is(200)->content_like(qr/login/i);
done_testing();

View File

@ -1,24 +1,26 @@
use Mojo::Base -strict;
use Test2::V0;
use Test2::Plugin::BailOnFail;
use Test::Mojo;
my $t = Test::Mojo->new('MyApp');
# Page zero gets a redirect.
$t->get_ok('/books/0')->status_is(302);
$t->get_ok('/0')->status_is(302);
# Now follow redirects and verify it's a 404.
$t->ua->max_redirects(10);
$t->get_ok('/books/0')->status_is(404);
$t->get_ok('/0')->status_is(404);
# Likewise for a too-high page number.
$t->get_ok('/books/999?title_like=qxqxqxqx')->status_is(404);
# A too-high page number should take us to the last page.
$t->get_ok('/999?title_like=qxqxqxqx')->status_is(200)
->content_like(qr/0\s+books\s+of\s+0/);
# Likewise for invalid page numbers.
$t->get_ok('/books/x')->status_is(404);
$t->get_ok('/books/1.99')->status_is(404);
$t->get_ok('/books/-42')->status_is(404);
$t->get_ok('/x')->status_is(404);
$t->get_ok('/1.99')->status_is(404);
$t->get_ok('/-42')->status_is(404);
# Or non-existent author.
$t->get_ok('/author/0/1')->status_is(404);

View File

@ -1,15 +1,8 @@
<p/>
Showing <%= $pager->entries_on_this_page %> <%= $items_name || "items" %>
of
<%= commify($pager->total_entries) =%>
% if ($pager->total_entries <= rpp) {
.
<% } else { %>
on page <%= commify($pager->current_page) %>
of <%= commify($pager->last_page) %>.
<div class="page-nav">
<br/>
<%= link_to url_with(page_number => 1) => begin %>◄ First<% end %>
% if ($pager->total_entries > rpp) {
<span>
<%= link_to url_with(page_number => 1) => begin %>◄ First<% end %>
% if (my $prev = $pager->previous_page) {
<%= link_to url_with(page_number => $prev) => begin %>◄ Prev<% end %>
@ -25,3 +18,18 @@ Next ►
<%= link_to url_with(page_number => $pager->last_page) => begin %>Last ►<% end %>
% }
</span>
% }
<span>
<%= $pager->entries_on_this_page %> <%= $items_name || "items" %>
of
<%= commify($pager->total_entries) =%>
% if ($pager->total_entries <= rpp) {
.
<% } else { %>
on page <%= commify($pager->current_page) %>
of <%= commify($pager->last_page) %>.
</span>
</div>

View File

@ -1,12 +0,0 @@
% layout 'default';
% title 'Account';
<h1><%= logname %>'s account on MyApp</h1>
%= form_for preferences => begin
%= label_for rows_per_page => 'Rows per page'
<%= number_field rows_per_page => $c->rpp,
min => 1, max => max_rpp, maxlength => 4, size => 4 %>
%= submit_button "Update"
% end
<p/>
%= flash('confirmation')

View File

@ -1,14 +0,0 @@
% layout 'default';
% title 'Auth';
<h1>Auth page</h1>
<%= "Login to reach " . session('post_login') . " page." if flash('naughty') %>
<%= $name_msg %>
<p/>
<div>
%= form_for login => begin
%= label_for logname => 'Name (only letters, please)'
%= text_field logname => flash('attempted_name')
%= submit_button "Login"
% end
</div>

View File

@ -2,16 +2,17 @@
% title 'Authors';
<h1><%= title %></h1>
<a href="/about#search-help">Search help</a>
<p/>
<%# On a new search, reset the page number to 1. %>
%= form_for authors => { page_number => 1 } => begin
<span>
%= label_for name_like => 'Name like'
%= text_field name_like => flash('name_like')
</span>
<span>
%= label_for minimum_book_count => 'Wrote at least'
<%= number_field minimum_book_count => 0,
min => 0, maxlength => 4, size => 4 %> books.
min => 0, maxlength => 4, size => 4 %> books
</span>
%= submit_button "Search"
%= end

View File

@ -2,15 +2,16 @@
% title 'Books';
<h1><%= title %></h1>
<a href="/about#search-help">Search help</a>
<p/>
<%# On a new search, reset the page number to 1. %>
%= form_for books => { page_number => 1 } => begin
<span>
%= label_for title_like => 'Title like'
%= text_field title_like => flash('title_like')
</span>
<span>
%= label_for author_like => 'Author like'
%= text_field author_like => flash('author_like')
</span>
%= submit_button "Search"
%= end

View File

@ -1,12 +0,0 @@
% layout 'default';
% title 'Home';
<h1>Home page</h1>
<p>Pages that don't require login:</p>
<p><%= link_to("About" => 'about') %></p>
<p><%= link_to("Books" => 'books' => { page_number => 1 }) %></p>
<p><%= link_to("Authors" => 'authors' => { page_number => 1 }) %></p>
<p>For this, you'll need to log in, then MyApp will redirect you:</p>
<p><%= link_to("Account" => 'account') %></p>

View File

@ -2,27 +2,42 @@
<html>
<head>
<title><%= title %></title>
<link rel="stylesheet" href="/default.css">
%= stylesheet '/default.css'
</head>
<body>
<ul class="top">
<li>
<%= link_to("About" => 'about') %>
</li>
<li>
<%= link_to("Authors" => 'authors') %>
</li>
<li>
<%= link_to("Books" => 'books') %>
</li>
<li>
<a href="<%= url_for('about') . '#search-help' %>">Search help</a>
</li>
<li>
%= form_for set_rpp => begin
<%= hidden_field target_url => url_with('current') %>
<%= number_field rows_per_page => $c->rpp,
min => 1, max => max_rpp, maxlength => 4, size => 4 %>
rows per page
%= submit_button "Update"
% end
</li>
</ul>
<hr/>
<div>
<%= content %>
</div>
<hr/>
<div>
<%# Provide a link to the home page, unless we're on it. %>
<%# I gave the home page route a name, else it's an empty string. %>
<% if ('home' ne current_route) { %>
<%= link_to("Home" => 'home') %>
<% } %>
</div>
<div>
<% if (logname) { %>
You are logged in as <%= logname %>.
<%= link_to("Logout" => 'logout') %>
<% } elsif (current_route ne 'auth') { %>
<%= link_to("Login" => 'auth') %>
<% } %>
</div>
</body>
</html>