Introduce caching for all query methods

Change-Id: Ibe43ccc18114f64d30e7e8f1162239daf78090c0
diff --git a/lib/Kalamar/Controller/Search2.pm b/lib/Kalamar/Controller/Search2.pm
index c7e9736..e16a744 100644
--- a/lib/Kalamar/Controller/Search2.pm
+++ b/lib/Kalamar/Controller/Search2.pm
@@ -15,6 +15,7 @@
 # TODO:
 #   Add match_info template for HTML
 
+
 # Query endpoint
 sub query {
   my $c = shift;
@@ -91,7 +92,8 @@
   # Create remote request URL
   my $url = Mojo::URL->new($c->korap->api);
   $url->path('search');
-  $url->query(\%query);
+  # $url->query(%query);
+  $url->query(map { $_ => $query{$_}} sort keys %query);
 
   # Check if total results is cached
   my $total_results = -1;
@@ -108,58 +110,19 @@
     $url->query({cutoff => 'true'});
   };
 
-  # Check if the request is cached
-  my $url_string = $url->to_string;
+  # Choose the snippet based on the parameter
+  # TODO:
+  #   scalar $v->param('snippet') ? 'snippet' : 'search2';
+  $c->stash(template => 'search2');
 
-  # Set api request for debugging
-  $c->stash(api_request => $url_string);
-
-  # Debugging
-  $c->app->log->debug("Search for $url_string");
-
-  # Check for cache
-  my $json = $c->chi->get("matches-$user-$url_string");
-
-  # Initialize promise object
-  my $promise;
-
-  # Result is cached
-  if ($json) {
-    $json->{cached} = 'true';
-
-    # The promise is already satisfied by the cache
-    $promise = Mojo::Promise->new->resolve($json)->then(
-      sub {
-        my $json = shift;
-        $c->notify_on_warnings($json);
-        return $json;
-      }
-    );
-  }
-
-  # Retrieve from URL
-  else {
-
-    # Wrap a user agent method with a promise
-    $promise = $c->user->auth_request_p(get => $url)->then(
-      sub {
-
-        # Catch errors and warnings
-        return $c->catch_errors_and_warnings(shift)
-      }
-    );
-  };
 
   # Wait for rendering
   $c->render_later;
 
-  # Choose the snippet based on the parameter
-  # scalar $v->param('snippet') ? 'snippet' : 'search2';
-  my $template = 'search2';
-  $c->stash(template => $template);
+  # Fetch resource
+  $c->cached_koral_p('get', $url)->then(
 
-  # Process response
-  $promise->then(
+    # Process response
     sub {
       my $json = shift;
 
@@ -200,9 +163,6 @@
         );
       };
 
-      # Cache result
-      $c->chi->set('matches-' . $user . '-' . $url_string => $json);
-
       # Process match results
       $c->_process_query_response($json);
 
@@ -270,17 +230,12 @@
     ))
   );
 
-  # Set query parameters
-  $url->query(\%query);
+  # Set query parameters in order
+  $url->query(map { $_ => $query{$_}} sort keys %query);
 
   $c->render_later;
 
-  # TODO: Add caching!
-  $c->user->auth_request_p(get => $url)->then(
-    sub {
-      return $c->catch_errors_and_warnings(shift);
-    }
-  )->then(
+  $c->cached_koral_p('get', $url)->then(
     sub {
       my $json = shift;
 
@@ -322,9 +277,6 @@
   my $v = $c->validation;
   $v->optional('cq');
 
-  # Async
-  $c->render_later;
-
   my $url = Mojo::URL->new($c->korap->api);
 
   # Use hash slice to create path
@@ -338,25 +290,11 @@
 
   $c->app->log->debug("Statistics info: $url");
 
-  # non-blocking
-  $c->user->auth_request_p(get => $url)->then(
-    sub {
-      return $c->catch_errors_and_warnings(shift);
-    }
-  )->then(
-    sub {
-      my $json = shift;
+  # Async
+  $c->render_later;
 
-      # TODO: CACHING!!!
-      # my $user = $c->stash('user') // 'not_logged_in';
-      # $c->stash('search.resource' => $json);
-      # $c->chi->set($user . $c->stash('search._resource_cache') => $json, '24 hours');
-
-      # $self->_process_response('resource', $index, $tx);
-
-      return $json;
-    }
-  )
+  # Request koral, maybe cached
+  $c->cached_koral_p('get', $url)
 
   # Process response
   ->then(
diff --git a/lib/Kalamar/Plugin/KalamarHelpers.pm b/lib/Kalamar/Plugin/KalamarHelpers.pm
index b6a67c9..7d1e1b8 100644
--- a/lib/Kalamar/Plugin/KalamarHelpers.pm
+++ b/lib/Kalamar/Plugin/KalamarHelpers.pm
@@ -1,7 +1,6 @@
 package Kalamar::Plugin::KalamarHelpers;
 use Mojo::Base 'Mojolicious::Plugin';
-use Mojo::JSON 'decode_json';
-use Mojo::JSON::Pointer;
+use Mojo::JSON qw/decode_json true false/;
 use Mojo::ByteStream 'b';
 use Mojo::Util qw/xml_escape/;
 
@@ -276,6 +275,68 @@
       return shift->config('Search')->{api};
     }
   );
+
+
+  # Get a cached request from the backend
+  $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') // 'not_logged_in';
+
+      # Set api request for debugging
+      my $cache_str = "$method-$user-" . $url->to_string;
+      $c->stash(api_request => $cache_str);
+
+      if ($c->no_cache) {
+        return $c->user->auth_request_p($method => $url)->then(
+          sub {
+            # Catch errors and warnings
+            return $c->catch_errors_and_warnings(shift)
+          }
+        );
+      };
+
+      # Get koral from cache
+      my $koral = $c->chi->get($cache_str);
+
+      my $promise;
+
+      # Cache was found
+      if ($koral) {
+
+        # Mark response as cache
+        $koral->{'X-cached'} = Mojo::JSON->true;
+
+        # The promise is already satisfied by the cache
+        return Mojo::Promise->new->resolve($koral)->then(
+          sub {
+            my $json = shift;
+            $c->notify_on_warnings($json);
+            return $json;
+          }
+        );
+      };
+
+      # Resolve request
+      return $c->user->auth_request_p($method => $url)->then(
+        sub {
+          # Catch errors and warnings
+          return $c->catch_errors_and_warnings(shift)
+        }
+      )->then(
+        # Cache on success
+        sub {
+          my $json = shift;
+          $c->chi->set($cache_str => $json);
+          return $json;
+        }
+      );
+    }
+  );
+
 };
 
 
diff --git a/t/corpus_info.t b/t/corpus_info.t
index e5d1987..1d8290b 100644
--- a/t/corpus_info.t
+++ b/t/corpus_info.t
@@ -31,6 +31,7 @@
   ->json_is('/tokens', 665842)
   ->json_is('/sentences', 25074)
   ->json_is('/paragraphs', 772)
+  ->json_is('/X-cached', undef)
   ;
 
 $t->get_ok('/corpus2?cq=docSigle+%3D+\"GOE/AGA\"')
@@ -39,6 +40,7 @@
   ->json_is('/tokens', 108557)
   ->json_is('/sentences', 3835)
   ->json_is('/paragraphs', 124)
+  ->json_is('/X-cached', undef)
   ;
 
 $t->get_ok('/corpus2?cq=4')
@@ -46,5 +48,16 @@
   ->json_is('/notifications/0/1', "302: Could not parse query >>> (4) <<<.")
   ;
 
+# Query passed
+$t->get_ok('/corpus2')
+  ->status_is(200)
+  ->json_is('/documents', 11)
+  ->json_is('/tokens', 665842)
+  ->json_is('/sentences', 25074)
+  ->json_is('/paragraphs', 772)
+  ->json_is('/X-cached', 1)
+  ;
+
+
 done_testing;
 __END__
diff --git a/t/match_info.t b/t/match_info.t
index 58191a5..c07e7aa 100644
--- a/t/match_info.t
+++ b/t/match_info.t
@@ -29,6 +29,7 @@
   ->status_is(200)
   ->json_is('/textSigle', 'WPD15/232/39681')
   ->json_like('/snippet', qr!<span class=\"context-left\">!)
+  ->json_is('/X-cached', undef)
   ;
 
 $t->get_ok('/corpus2/GOE/AGF/02286/p75682-75683')
@@ -89,6 +90,14 @@
   ->json_is('/notifications/0/1', 'Message structure failed')
   ;
 
+# Get from cache
+$t->get_ok('/corpus2/WPD15/232/39681/p2133-2134?spans=false&foundry=*')
+  ->status_is(200)
+  ->json_is('/textSigle', 'WPD15/232/39681')
+  ->json_like('/snippet', qr!<span class=\"context-left\">!)
+  ->json_is('/X-cached', 1)
+  ;
+
 
 done_testing;
 __END__