package Routes::Moongate; use Dancer2 appname => 'DocsLib'; use v5.34.0; use Path::Tiny; use Data::Printer; use App::Model; # D2 plugin, provides 'model' keyword use Dancer2::Plugin::Deferred; use Dancer2::Plugin::Auth::Tiny; my $PREFIX = 'moongate'; prefix "/$PREFIX"; my $docs_path = path( config->{documents_path}, $PREFIX ); # debug $docs_path; # set path to documents folder: model->moongate->set_docs_path($docs_path); hook before_template_render => sub { my $tokens = shift; # need to have unique token name across all route files my %h = ( home => '/', new => '/new_document', edit => '/edit', create => '/create', search => '/search', update => '/update', summary => '/summary', document => '/id', download => '/download', ); $tokens->{uri_for_section}->{$PREFIX}->{$_} = uri_for('/'. $PREFIX . $h{$_}) for keys %h; # p $tokens; }; =begin # rules for route names & uri_for_route: cannot use method 'any' with route names: "Route with this name ($name) already exists" probably when route name is registered for GET, then for POST/HEAD/etc cannot use uri_for_route in any template loaded after forward (data is deleted) =cut # these routes need unique (to DocsLib app) route-names (for redirect): get moongate_home => '/' => needs login => \&home; get moongate_entry => '/id/:id[Int]' => \&get_document; # these routes need permissions: get '/edit/:id[Int]' => needs login => \&edit_document; post '/create' => needs login => \&create_document; post '/update/:id[Int]' => needs login => \&update_document; # these routes don't need names or permissions get '/new_document' => \&new_document; get '/summary' => \&summary; get '/download/:id[Str]' => \&download; post '/search' => \&search; # ============================================================================== sub flash { deferred @_ } sub home { template home => { title => 'Moongate' } }; sub new_document { var next_route => 'create'; template $PREFIX.'/record.tt', { prefix => $PREFIX }; }; sub get_document { my $id = route_parameters->get('id'); my $rec = model->moongate->get_document($id); # p $rec; # AoH vars->{next_route} ||= 'edit/'.$id; # may already be set by edit_document() template $PREFIX.'/record.tt', { entry => $rec }; }; sub edit_document { my $id = route_parameters->get('id'); var next_route => 'update/'.$id; my $route = '/'.$PREFIX.'/id/'.$id; forward $route, {}, { method => 'GET' }; }; sub create_document { 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 both document id & data_file submitted, must be new data file so get # old filename and store in var for later deletion: if ( $data_file && $params->{id} ) { my $rec = model->moongate->get_document($params->{id}); # p $rec; # AoH var file_to_delete => $rec->{filename}; } my $res = model->moongate->save_document($params, $data_file); # p $res; if ( $res->{error} ) { var input_error => $res->{error}; template $PREFIX.'/record.tt', { entry => $params }; } else { my $action = $params->{id} ? 'update_success' : 'input_success'; # update if id supplied flash ( $action => 1 ); # doesn't need message if ($data_file) { # upload file to docs dir if uploaded: my $filepath = path($docs_path, $params->{filename}); debug 'uploading ' . $filepath; unless ( $data_file->copy_to($filepath) ) { # true or success, otherwise sets $! my $err = $!; warning "upload $filepath failed: $err"; send_error "upload $filepath failed: $err", 500; } # delete old file if new file submitted if ( my $old_filename = vars->{file_to_delete} ) { my $filepath = path( $docs_path, $old_filename ); # p $filepath; debug 'deleting old file: ' . $filepath; unless ( unlink( $filepath ) ) { my $err = $!; warning "deleting $filepath failed: $err"; send_error "deleting $filepath failed: $err", 500; } } } redirect uri_for_route( 'moongate_entry', { id => $res->{id} } ); } }; sub update_document { # just captures document_id & forwards: forward '/'.$PREFIX.'/create', { id => route_parameters->get('id') }; }; sub summary { my $rec = model->moongate->get_all_documents; # p $rec; # AoH template $PREFIX.'/summary.tt', { records => $rec }; }; sub search { my $str = body_parameters->get('search'); # =~ s{\s+}{}gr; sometimes need space my $rec = model->moongate->find_documents($str); # p $rec; # AoH template $PREFIX.'/summary.tt', { records => $rec }; }; sub download { my $dir = Path::Tiny->new($docs_path); # normalize to absolute path unless already (returns 'not found' using relative paths): $dir = $dir->absolute( config->{appdir} ) unless $dir->is_absolute; debug "\$dir: $dir"; my $doc = route_parameters->get('id'); debug "\$doc: $doc"; my $file = $dir->child($doc); debug "Resolved file path: $file"; my $util = App::Utils->new; # validator returns 'OK', or arrayref my $validation = $util->validate_download_request($dir, $doc, $file); if ( ref $validation eq 'ARRAY' ) { my ($error, $code) = @$validation; warning "failed validation of download: $doc ($error)"; send_error $error, $code; } send_file $file->stringify, system_path => 1; }; true;