RSS Git Download  Clone
Raw Blame History
use 5.34.0;
package Routes;

use Dancer2;
use Dancer2::Plugin::Deferred;
use Dancer2::Plugin::Auth::Tiny;
use Dancer2::Plugin::CryptPassphrase; # this is (deliberately) slow due to hashing

use IO::All;
use Data::Printer;

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

use App::Model;
my $app = App::Model->new( cfg => config() ); # p $app;

our $VERSION = $app->version; say 'App version: '.$VERSION;

my $docs_path = path( config->{appdir}, 'documents' ); # p $docs_path;

hook before => sub { };

hook before_template_render => sub {
    my $tokens = shift; # $tokens->{env} = p %ENV;
    $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 };
};

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 '/search' => sub {
	my $str = body_parameters->get('search');
	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 '/update/:id[Int]' => needs login => sub { # just captures document id & forwards:
	forward '/service_record', # document_id becomes query_parameter when forwarded (??):
		{ id => route_parameters->get('id') };
};

post '/login' => sub { # my $p = params(); p $p;
    my $password   = body_parameters->get('password');
    # get return_url (not used for this app):
    my $return_url = body_parameters->get('return_url'); # say $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 };
    }
};

post '/service_record' => needs login => sub {
    my $params = request->parameters->as_hashref; # p $params;
    # get upload data - new entry only, passed as param if edit:
    my $data_file = upload('filename'); # p $data_file;
    if ( $data_file ) {
        my $filename = $data_file->filename =~ s{\s}{_}g; # spaces -> underscores
        # generate $filepath from docs_path & filename, create var of same name:
        my $filepath = var filepath => path($docs_path, $filename); # p $filepath;
        # check if it exists and bail if it does:
        if ( -e $filepath ) {
            var input_error => qq!file "$filename" already exists!;
            forward '/', { records => [$params] }, { method => 'GET' }; # tt needs AoH
        }
        # add filename to params:
        $params->{filename} = $filename; # say vars->{filepath};
    } # p $params;

    my $res = $app->save_document($params); # p $res;
	if ( $res->{error} ) {
		var input_error => $res->{error};
		forward '/', { records => [$params] }, { method => 'GET' };
    }
	else {
		deferred input_success => 1; # doesn't need message
        # upload file to docs dir if uploaded:
        $data_file->copy_to( vars->{filepath} ) if $data_file;
		redirect uri_for '/id/' . $res->{id};
	}
};

get '/download/*' => sub {
    my ($document) = splat;
    my $file = join '/', $docs_path, $document; # p $filename;
    send_file $file, system_path => 1;
};

get '/gitlog' => sub { template gitlog => { log => $app->gitlog } };

# this isn't needed except for testing:
get '/logout' => sub { app->destroy_session; redirect '/' };

get '/dancr' => sub { template index => {}, { layout => 'default' } };

#  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;