package Routes;
use 5.24.0; # say
use Data::Printer;
use Time::HiRes qw(gettimeofday tv_interval);
use Dancer2;
use Dancer2::Plugin::Deferred;
use Dancer2::Plugin::Auth::Tiny;
use Dancer2::Plugin::CryptPassphrase; # this is (deliberately) slow due to hashing
use App::Model;
use constant PERLVERSION => $^V;
use if PERLVERSION lt v5.36.0, experimental => 'signatures'; # but not for routes
set auto_page => 1; # maybe not in use, but useful anyway
my $app = App::Model->new( cfg => config() ); # p $app->model;
our $VERSION = $app->app_version; say "App version: $VERSION";
hook before => sub {
var t0 => [gettimeofday];
};
hook before_template_render => sub {
my $tokens = shift;
$tokens->{app_version} = $VERSION;
$tokens->{total_count} = $app->total_count;
$tokens->{css_url} = request->base . '/css'; # uri_for's called directly in .tt
};
get '/' => needs login => sub { # my $s = session; p $s;
template home => { records => query_parameters->get('records') };
};
# for redirect after successful submit:
get '/id/:id[Int]' => sub {
my $id = route_parameters->get('id');
my $rec = $app->get_document($id); # p $rec; # AoH
template home => { records => $rec };
};
post '/search' => sub {
my $str = body_parameters->get('search') =~ s{\s+}{}gr;
my $rec = $app->find_documents($str); # p $rec; # AoH
template home => { records => $rec };
};
post '/edit/:id[Int]' => needs login => sub {
var is_edit => 1;
forward '/id/' . route_parameters->get('id'), {}, { method => 'GET' };
};
post '/new_document' => needs login => sub {
my $content = body_parameters->get('content');
my $title = body_parameters->get('title');
# if update, get document_id as a param, captured in /update/:document_id
# route and forwarded as a query param:
my $doc_id = query_parameters->get('document_id');
my %d = (
content => $content,
title => $title,
id => $doc_id, # only exists for update
); # p %d;
my $res = $app->save_document(\%d); # p $res;
if ( $res->{error} ) {
var input_error => $res->{error};
forward '/', { records => [ \%d ] }, { method => 'GET' }; # tt needs AoH
}
else {
deferred input_success => 1; # doesn't need message
redirect uri_for '/id/' . $res->{id};
}
};
post '/update/:document_id' => needs login => sub { # just captures document_id & forwards:
forward '/new_document', # document_id becomes query_parameter when forwarded (??):
{ document_id => route_parameters->get('document_id') };
};
get '/all_docs' => sub {
my $rec = $app->get_all; # p $rec; # AoH
template home => { records => $rec };
};
get '/login' => sub { # my $p = params(); p $p;
my $return_url = query_parameters->get('return_url'); # set by Auth::Tiny
template login => { return_url => $return_url }
};
post '/login' => sub { # my $p = params(); p $p;
my $password = body_parameters->get('password');
my $return_url = body_parameters->get('return_url'); # p $return_url;
if ( my $username = _authenticate_user($password) ) {
app->change_session_id;
session user => $username; # Plugin::Auth::Tiny requires session id = 'user'
info "$username successfully logged in";
redirect $return_url || '/';
}
else {
$username = body_parameters->get('username') || 'unknown user';
warning "failed login attempt for $username";
template login => { login_error => 1, return_url => $return_url };
}
};
get '/gitlog' => sub { template gitlog => { log => $app->gitlog } };
# this isn't needed except for testing:
get '/logout' => sub { app->destroy_session; redirect '/' };
# using PBKDF2 for development, faster but less secure than Argon2 for deployment
sub _authenticate_user ($password) {
my $username = body_parameters->get('username');
if ( grep app->environment eq $_, qw/development test/ ) { # use faster PBKDF2 method
my $user = setting 'user'; # in development_local.yml
$username ||= $user->{name}; # don't need to provide it for dev
return $username if $app->verify_password( $password, $user->{pwd} );
}
else { # Plugin::CryptPassphrase (uses more secure argon2 auth)
my $user = $app->find_user($username);
return $username if verify_password( $password, $user->{password} );
}
return undef; # failed authentication
}
true;