Improve error handling

Change-Id: I1f54cf9cd4770d6602f70036cf0e27c9ede8c893
diff --git a/lib/Kalamar.pm b/lib/Kalamar.pm
index 340d1fc..f5151b4 100644
--- a/lib/Kalamar.pm
+++ b/lib/Kalamar.pm
@@ -116,7 +116,8 @@
   # Client notifications
   $self->plugin(Notifications => {
     'Kalamar::Plugin::Notifications' => 1,
-    JSON => 1
+    JSON => 1,
+    'HTML' => 1
   });
 
   # Localization framework
@@ -208,10 +209,9 @@
 
   # Base query route
   $r->get('/')->to('search2#query')->name('index');
-  $r->get('/q2')->to('search2#query');
 
-  # Collection route
-  $r->get('/corpus')->to('Search#corpus_info')->name('corpus');
+  # Corpus route
+  $r->get('/corpus')->to('Search2#corpus_info')->name('corpus');
 
   # Documentation routes
   $r->get('/doc')->to('documentation#page', page => 'korap')->name('doc_start');
@@ -225,11 +225,8 @@
   # Match route
   my $corpus = $r->route('/corpus/:corpus_id');
   my $doc    = $corpus->get('/:doc_id');
-  my $text   = $doc->get('/:text_id')->to('search#text_info')->name('text');
-  my $match  = $doc->get('/:text_id/:match_id')->to('search#match_info')->name('match');
-
-  $r->get('/corpus2')->to('Search2#corpus_info')->name('corpus');
-  $r->route('/corpus2/:corpus_id/:doc_id/:text_id/:match_id')->to('search2#match_info')->name('match');
+  my $text   = $doc->get('/:text_id')->to('search2#text_info')->name('text');
+  my $match  = $doc->get('/:text_id/:match_id')->to('search2#match_info')->name('match');
 
   # User Management
   my $user = $r->any('/user')->to(controller => 'User');
diff --git a/lib/Kalamar/Controller/Search2.pm b/lib/Kalamar/Controller/Search2.pm
index 397d7c5..7922a2a 100644
--- a/lib/Kalamar/Controller/Search2.pm
+++ b/lib/Kalamar/Controller/Search2.pm
@@ -23,9 +23,6 @@
   # Validate user input
   my $v = $c->validation;
 
-  # In case the user is not known, it is assumed, the user is not logged in
-  my $user = $c->stash('user') // 'not_logged_in';
-
   $v->optional('q', 'trim');
   $v->optional('ql')->in(qw/poliqarp cosmas2 annis cql fcsql/);
   $v->optional('collection', 'trim'); # Legacy
@@ -50,28 +47,33 @@
   };
 
   my %query = ();
-  $query{q}       = $v->param('q');
+  $query{q}       = $query;
   $query{ql}      = $v->param('ql') // 'poliqarp';
-  $query{p}       = $v->param('p') // 1; # Start page
   $query{count}   = $v->param('count') // $c->items_per_page;
   $query{cq}      = $v->param('cq') // $v->param('collection');
   $query{cutoff}  = $v->param('cutoff');
-
   # Before: 'base/s:p'/'paragraph'
   $query{context} = $v->param('context') // '40-t,40-t';
 
+  # Start page
+  my $page = $v->param('p') // 1;
+
+  $c->stash(query => $query);
+  $c->stash(ql => $query{ql});
+
   my $items_per_page = $c->items_per_page;
 
   # Set count
   if ($query{count} && $query{count} <= $c->items_per_page ) {
     $items_per_page = delete $query{count};
+    $query{count} = $items_per_page;
   };
 
   $c->stash(items_per_page => $items_per_page);
 
   # Set offset
   # From Mojolicious::Plugin::Search::Index
-  $query{o} = $v->param('o') || ((($query{p} // 1) - 1) * ($items_per_page || 1));
+  $query{offset} = $v->param('o') || ((($page // 1) - 1) * ($items_per_page || 1));
 
 
   # already set by stash - or use plugin param
@@ -95,27 +97,35 @@
   # $url->query(%query);
   $url->query(map { $_ => $query{$_}} sort keys %query);
 
-  # Check if total results is cached
+  # In case the user is not known, it is assumed, the user is not logged in
+  my $total_cache_str;
+
+  # Check if total results information is cached
   my $total_results = -1;
   unless ($c->no_cache) {
 
+    # Create cache string
+    my $user = $c->user->handle;
+    my $cache_url = $url->clone;
+    $cache_url->query->remove('context')->remove('count')->remove('cutoff')->remove('offset');
+    $total_cache_str = "total-$user-" . $cache_url->to_string;
+
+    $c->app->log->debug('Check for total results: ' . $total_cache_str);
+
     # Get total results value
-    $total_results = $c->chi->get('total-' . $user . '-' . $url->to_string);
+    $total_results = $c->chi->get($total_cache_str);
 
     # Set stash if cache exists
-    $c->stash(total_results => $total_results) if $total_results;
-    $c->app->log->debug('Get total result from cache');
+    if (defined $total_results) {
+      $c->stash(total_results => $total_results);
 
-    # Set cutoff unless already set
-    $url->query({cutoff => 'true'});
+      $c->app->log->debug('Get total result from cache: ' . $total_results);
+
+      # Set cutoff unless already set
+      $url->query({cutoff => 'true'});
+    };
   };
 
-  # Choose the snippet based on the parameter
-  # TODO:
-  #   scalar $v->param('snippet') ? 'snippet' : 'search2';
-  $c->stash(template => 'search2');
-
-
   # Wait for rendering
   $c->render_later;
 
@@ -132,26 +142,30 @@
       # The stash is set in case the total results value is from the cache,
       # so in that case, it does not need to be cached again
       my $total_results = $c->stash('total_results');
-      if (!$total_results) {
+
+      unless (defined $total_results) {
 
         # There are results to remember
         if ($json->{meta}->{totalResults} >= 0) {
 
           # Remove cutoff requirement again
-          $url->query([cutoff => 'true']);
+          # $url->query([cutoff => 'true']);
 
           $total_results = $json->{meta}->{totalResults};
           $c->stash(total_results => $total_results);
 
+          $c->app->log->debug('Set for total results: ' . $total_cache_str);
+
           # Set cache
-          $c->chi->set(
-            'total-' . $user . '-' . $url->to_string => $total_results
-          );
+          $c->chi->set($total_cache_str => $total_results);
+        }
+
+        # Undefined total results
+        else {
+          $c->stash(total_results => -1);
         };
-      }
-      else {
-        $c->stash(total_results => -1);
-      }
+      };
+
 
       $c->stash(total_pages => 0);
 
@@ -163,28 +177,76 @@
         );
       };
 
-      # Process match results
-      $c->_process_query_response($json);
+      # Process meta
+      my $meta = $json->{meta};
+
+      # TODO:
+      #   Set benchmark in case of development mode only.
+      #   Use server timing API
+      #
+      # Reformat benchmark counter
+      # my $benchmark = $meta->{benchmark};
+      # if ($benchmark && $benchmark =~ s/\s+(m)?s$//) {
+      #   $benchmark = sprintf("%.2f", $benchmark) . ($1 ? $1 : '') . 's';
+      # };
+      #
+      # # Set benchmark
+      # $self->stash(benchmark => $benchmark);
+
+      # Set time exceeded
+      if ($meta->{timeExceeded} &&
+            $meta->{timeExceeded} eq Mojo::JSON::true) {
+        $c->stash(time_exceeded => 1);
+      };
+
+      # Set result values
+      $c->stash(items_per_page => $meta->{itemsPerPage});
+
+      ## Bouncing query
+      ##  if ($json->{query}) {
+      ##    $index->query_jsonld($json->{query});
+      ##  };
+
+      ## Legacy
+      ## elsif ($json->{request}->{query}) {
+      ##   $index->query_jsonld($json->{request}->{query});
+      ## };
+
+      # Bouncing collection query
+      if ($json->{corpus} || $json->{collection}) {
+        $c->stash(corpus_jsonld => ($json->{corpus} || $json->{collection}));
+      };
+
+      # TODO:
+      #   scalar $v->param('snippet') ? 'snippet' : 'search2';
 
       # Render result
       return $c->render(
-        q => $query,
-        ql => $query{ql},
-        start_page => $query{p},
+        q => $c->stash('query'),
+        ql => $c->stash('ql'),
+        start_page => $page,
+        start_index => $json->{meta}->{startIndex},
+        results => _map_matches($json->{matches}),
+        template => 'search2'
       );
-
     }
 
       # Deal with errors
   )->catch(
     sub {
+      my $err_msg = shift;
+
+      # Only raised in case of connection errors
+      if ($err_msg) {
+        $c->stash('err_msg' => 'backendNotAvailable');
+        $c->notify(error => { src => 'Backend' } => $err_msg)
+      };
 
       # $c->_notify_on_errors(shift);
       return $c->render(
-        results => c(),
-        q => $query,
-        ql => $query{ql},
-        start_page => 1
+        q => $c->stash('query'),
+        ql => $c->stash('ql'),
+        template => 'failure'
       );
     }
   )
@@ -353,63 +415,6 @@
 };
 
 
-# Process response and set stash values
-sub _process_query_response {
-  my ($self, $json) = @_;
-
-    # Process meta
-  my $meta = $json->{meta};
-
-  # TODO:
-  #   Set benchmark in case of development mode only.
-  #   Use server timing API
-  #
-  # Reformat benchmark counter
-  # my $benchmark = $meta->{benchmark};
-  # if ($benchmark && $benchmark =~ s/\s+(m)?s$//) {
-  #   $benchmark = sprintf("%.2f", $benchmark) . ($1 ? $1 : '') . 's';
-  # };
-  #
-  # # Set benchmark
-  # $self->stash(benchmark => $benchmark);
-
-  # Set time exceeded
-  if ($meta->{timeExceeded} && $meta->{timeExceeded} eq Mojo::JSON::true) {
-    $self->stash(time_exceeded => 1);
-  };
-
-  # Set result values
-  $self->stash(items_per_page => $meta->{itemsPerPage});
-
-  ## Bouncing query
-  ##  if ($json->{query}) {
-  ##    $index->query_jsonld($json->{query});
-  ##  };
-
-  ## Legacy
-  ## elsif ($json->{request}->{query}) {
-  ##   $index->query_jsonld($json->{request}->{query});
-  ## };
-
-
-  if ($meta->{totalResults}) {
-    $self->stash(total_results => $meta->{totalResults});
-  };
-
-  # Bouncing collection query
-  if ($json->{corpus} || $json->{collection}) {
-    $self->stash(corpus_jsonld => ($json->{corpus} || $json->{collection}));
-  };
-
-  # Set results to stash
-  $self->stash(
-    results => _map_matches($json->{matches})
-  );
-
-  return;
-};
-
-
 1;
 
 
diff --git a/lib/Kalamar/Plugin/KalamarErrors.pm b/lib/Kalamar/Plugin/KalamarErrors.pm
index fd9ead3..2c897fc 100644
--- a/lib/Kalamar/Plugin/KalamarErrors.pm
+++ b/lib/Kalamar/Plugin/KalamarErrors.pm
@@ -59,11 +59,11 @@
     }
   );
 
-  # Catch connection errors
+  # Catch errors and warnings
+  # This won't be called for connection errors!
   $mojo->helper(
     catch_errors_and_warnings => sub {
       my ($c, $tx) = @_;
-
       my $err = $tx->error;
 
       if ($err && $err->{code} != 500) {
@@ -79,17 +79,19 @@
       if (!$json && !$err) {
 
         $c->notify(error => 'JSON response is invalid');
-        return Mojo::Promise->new->reject;
+        return; # Mojo::Promise->new->reject;
       };
 
       # There is json
       if ($json) {
 
+        $c->stash(api_response => $json);
+
         # There are errors
         if ($c->notify_on_errors($json)) {
 
           # Return on errors - ignore warnings
-          return Mojo::Promise->new->reject;
+          return;# Mojo::Promise->new->reject;
         };
 
         # Notify on warnings
@@ -99,7 +101,7 @@
         if ($json->{status}) {
 
           $c->notify(error => 'Middleware error ' . $json->{'status'});
-          return Mojo::Promise->new->reject;
+          return;# Mojo::Promise->new->reject;
         };
       }
 
@@ -108,7 +110,7 @@
 
         # Send rejection promise
         $c->notify(error => $err->{code} . ': ' . $err->{message});
-        return Mojo::Promise->new->reject;
+        return; #Mojo::Promise->new->reject;
       };
 
       return $json;
diff --git a/lib/Kalamar/Plugin/KalamarHelpers.pm b/lib/Kalamar/Plugin/KalamarHelpers.pm
index b0006c3..6e0b228 100644
--- a/lib/Kalamar/Plugin/KalamarHelpers.pm
+++ b/lib/Kalamar/Plugin/KalamarHelpers.pm
@@ -277,23 +277,14 @@
   );
 
 
-  # Get a cached request from the backend
+  # Get a cached request from the backend as a promise
   $mojo->helper(
     cached_koral_p => sub {
       my ($c, $method, $url) = @_;
 
       # In case the user is not known, it is assumed,
       # the user is not logged in
-      my $user = $c->stash('user');
-      unless ($user) {
-        $user = $c->session('user');
-        if ($user) {
-          $c->stash(user => $user);
-        }
-        else {
-          $user = 'not_logged_in';
-        }
-      };
+      my $user = $c->user->handle;
 
       # Set api request for debugging
       my $cache_str = "$method-$user-" . $url->to_string;
@@ -302,10 +293,10 @@
       if ($c->no_cache) {
         return $c->user->auth_request_p($method => $url)->then(
           sub {
-            my $json = shift;
+            my $tx = shift;
             # Catch errors and warnings
-            $c->stash(api_response => $json);
-            return $c->catch_errors_and_warnings($json);
+            return ($c->catch_errors_and_warnings($tx) ||
+              Mojo::Promise->new->reject);
           }
         );
       };
@@ -319,7 +310,7 @@
       if ($koral) {
 
         # Mark response as cache
-        $koral->{'X-cached'} = Mojo::JSON->true;
+        $c->res->headers->add('X-Kalamar-Cache' => 'true');
 
         # The promise is already satisfied by the cache
         return Mojo::Promise->new->resolve($koral)->then(
@@ -335,17 +326,15 @@
       # Resolve request
       return $c->user->auth_request_p($method => $url)->then(
         sub {
-          my $json = shift;
-          # Catch errors and warnings
-          $c->stash(api_response => $json);
-          return $c->catch_errors_and_warnings($json);
+          my $tx = shift;
+          return ($c->catch_errors_and_warnings($tx) ||
+                    Mojo::Promise->new->reject);
         }
       )->then(
         # Cache on success
         sub {
           my $json = shift;
           $c->chi->set($cache_str => $json);
-          $c->stash(api_response => $json);
           return $json;
         }
       );
diff --git a/lib/Kalamar/Plugin/KalamarUser.pm b/lib/Kalamar/Plugin/KalamarUser.pm
index 41251a1..19bb2a7 100644
--- a/lib/Kalamar/Plugin/KalamarUser.pm
+++ b/lib/Kalamar/Plugin/KalamarUser.pm
@@ -86,6 +86,29 @@
     }
   );
 
+  # Get user handle
+  $mojo->helper(
+    'user.handle' => sub {
+      my $c = shift;
+
+      # Get from stash
+      my $user = $c->stash('user');
+      return $user if $user;
+
+      # Get from session
+      $user = $c->session('user');
+
+      # Set in stash
+      if ($user) {
+        $c->stash(user => $user);
+        return $user;
+      };
+
+      return 'not_logged_in';
+    }
+  );
+
+
   # Request with authorization header
   $mojo->helper(
     'user.auth_request' => sub {
diff --git a/lib/Kalamar/Plugin/Notifications.pm b/lib/Kalamar/Plugin/Notifications.pm
index 9fea51b..8b6df94 100644
--- a/lib/Kalamar/Plugin/Notifications.pm
+++ b/lib/Kalamar/Plugin/Notifications.pm
@@ -21,6 +21,9 @@
   foreach (@$notify_array) {
     $js .= 'KorAP.Notifications.push([';
     $js .= quote($_->[0]) . ',' . quote($_->[-1]);
+    if (ref $_->[1] && ref $_->[1] eq 'HASH') {
+      $js .= ',' . quote($_->[1]->{src}) if $_->[1]->{src};
+    };
     $js .= "]);\n";
 
     $noscript .= qq{<div class="notify notify-} . $_->[0] . '">' .