Support text information endpoint

Change-Id: I93499d56e235f99dd56189e0174b5c572ae36189
diff --git a/lib/Kalamar.pm b/lib/Kalamar.pm
index f5151b4..1fa6c52 100644
--- a/lib/Kalamar.pm
+++ b/lib/Kalamar.pm
@@ -210,9 +210,6 @@
   # Base query route
   $r->get('/')->to('search2#query')->name('index');
 
-  # Corpus route
-  $r->get('/corpus')->to('Search2#corpus_info')->name('corpus');
-
   # Documentation routes
   $r->get('/doc')->to('documentation#page', page => 'korap')->name('doc_start');
   $r->get('/doc/:page')->to('documentation#page', scope => undef);
@@ -223,8 +220,9 @@
   $r->get('/contact')->mail_to_chiffre('documentation#contact');
 
   # Match route
-  my $corpus = $r->route('/corpus/:corpus_id');
-  my $doc    = $corpus->get('/:doc_id');
+  # Corpus route
+  my $corpus = $r->get('/corpus')->to('Search2#corpus_info')->name('corpus');
+  my $doc    = $r->route('/corpus/:corpus_id/:doc_id');
   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');
 
@@ -234,9 +232,6 @@
   $user->get('/logout')->to(action => 'logout')->name('logout');
   # $r->any('/register')->to(action => 'register')->name('register');
   # $r->any('/forgotten')->to(action => 'pwdforgotten')->name('pwdforgotten');
-
-  # Default user is called 'korap'
-  # $r->route('/user/:user/:collection')
 };
 
 
diff --git a/lib/Kalamar/Controller/Search2.pm b/lib/Kalamar/Controller/Search2.pm
index 7922a2a..f071fbf 100644
--- a/lib/Kalamar/Controller/Search2.pm
+++ b/lib/Kalamar/Controller/Search2.pm
@@ -14,6 +14,13 @@
 
 # TODO:
 #   Add match_info template for HTML
+#
+# TODO:
+#   Support search in corpus and virtualcollection
+#
+# TODO:
+#   set caches with timing like '120min'
+
 
 
 # Query endpoint
@@ -71,26 +78,13 @@
 
   $c->stash(items_per_page => $items_per_page);
 
+  # TODO:
+  #   if ($v->param('action') eq 'inspect') use trace!
+
   # Set offset
   # From Mojolicious::Plugin::Search::Index
   $query{offset} = $v->param('o') || ((($page // 1) - 1) * ($items_per_page || 1));
 
-
-  # already set by stash - or use plugin param
-  # else {
-  #   $items_per_page = $c->stash('search.count') // $plugin->items_per_page
-  # };
-
-  # Set start page based on param
-  #if ($query{p}) {
-  #  $index->start_page(delete $param{start_page});
-  #}
-  ## already set by stash
-  #elsif ($c->stash('search.start_page')) {
-  #  $index->start_page($c->stash('search.start_page'));
-  #};
-
-
   # Create remote request URL
   my $url = Mojo::URL->new($c->korap->api);
   $url->path('search');
@@ -202,16 +196,6 @@
       # 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}));
@@ -258,6 +242,115 @@
 };
 
 
+# Corpus info endpoint
+# This replaces the collections endpoint
+sub corpus_info {
+  my $c = shift;
+
+  # Input validation
+  my $v = $c->validation;
+  $v->optional('cq');
+
+  my $url = Mojo::URL->new($c->korap->api);
+
+  # Use hash slice to create path
+  $url->path('statistics');
+
+  # Add query
+  $url->query(corpusQuery => $v->param('cq'));
+
+  $c->app->log->debug("Statistics info: $url");
+
+  # Async
+  $c->render_later;
+
+  # Request koral, maybe cached
+  $c->cached_koral_p('get', $url)
+
+  # Process response
+  ->then(
+    sub {
+      my $json = shift;
+      return $c->render(
+        json => $c->notifications(json => $json),
+        status => 200
+      );
+    }
+  )
+
+  # Deal with errors
+  ->catch(
+    sub {
+      return $c->render(
+        json => $c->notifications('json')
+      )
+    }
+  )
+
+  # Start IOLoop
+  ->wait;
+
+  return 1;
+};
+
+
+# Text info endpoint
+sub text_info {
+  my $c = shift;
+
+  # Input validation
+  my $v = $c->validation;
+  $v->optional('fields');
+
+  my %query = (fields => '@all');
+  $query{fields} = $v->param('fields') if $v->param('fields');
+
+  my $url = Mojo::URL->new($c->korap->api);
+
+  # Use hash slice to create path
+  $url->path(
+    join('/', (
+      'corpus',
+      $c->stash('corpus_id'),
+      $c->stash('doc_id'),
+      $c->stash('text_id')
+    ))
+  );
+  $url->query(%query);
+
+  # Async
+  $c->render_later;
+
+  # Request koral, maybe cached
+  $c->cached_koral_p('get', $url)
+
+  # Process response
+  ->then(
+    sub {
+      my $json = shift;
+      return $c->render(
+        json => $c->notifications(json => $json),
+        status => 200
+      );
+    }
+  )
+
+  # Deal with errors
+  ->catch(
+    sub {
+      return $c->render(
+        json => $c->notifications('json')
+      )
+    }
+  )
+
+  # Start IOLoop
+  ->wait;
+
+  return 1;
+};
+
+
 # Match info endpoint
 sub match_info {
   my $c = shift;
@@ -330,61 +423,6 @@
 };
 
 
-# Get information about
-# This replaces the collections endpoint
-sub corpus_info {
-  my $c = shift;
-
-  # Input validation
-  my $v = $c->validation;
-  $v->optional('cq');
-
-  my $url = Mojo::URL->new($c->korap->api);
-
-  # Use hash slice to create path
-  $url->path('statistics');
-
-  # Add query
-  $url->query(corpusQuery => $v->param('cq'));
-
-  # Set stash
-  $c->stash('search._resource_cache' => $url->to_string);
-
-  $c->app->log->debug("Statistics info: $url");
-
-  # Async
-  $c->render_later;
-
-  # Request koral, maybe cached
-  $c->cached_koral_p('get', $url)
-
-  # Process response
-  ->then(
-    sub {
-      my $json = shift;
-      return $c->render(
-        json => $c->notifications(json => $json),
-        status => 200
-      );
-    }
-  )
-
-  # Deal with errors
-  ->catch(
-    sub {
-      return $c->render(
-        json => $c->notifications('json')
-      )
-    }
-  )
-
-  # Start IOLoop
-  ->wait;
-
-  return 1;
-};
-
-
 # Cleanup array of matches
 sub _map_matches {
   return c() unless $_[0];
@@ -419,3 +457,178 @@
 
 
 __END__
+
+__END__
+
+=pod
+
+=encoding utf8
+
+=head1 NAME
+
+Kalamar::Controller::Search
+
+
+=head1 DESCRIPTION
+
+L<Kalamar::Controller::Search> is the controller class for
+search related endpoints in Kalamar. Actions are released when routes
+match.
+
+
+=head1 METHODS
+
+L<Kalamar::Controller::Search> inherits all methods from
+L<Mojolicious::Controller> and implements the following new ones.
+
+=head2 query
+
+  GET /?q=Baum&ql=poliqarp
+
+Action for all queries to the system. Returns C<HTML> only for the moment.
+
+The following parameters are supported.
+
+
+=over 2
+
+=item B<q>
+
+The query string. This may any query written in a supported query language.
+
+
+=item B<ql>
+
+The query language. This may be any query language supported by the system,
+written as the API expects the string.
+
+
+=item B<action>
+
+May be C<inspect>. In that case, the serialized request is mirrored instead of
+processed.
+
+B<This switch is experimental and may change without warnings!>
+
+
+=item B<snippet>
+
+If set, the query is returned in the snippet view template.
+
+B<This parameter is experimental and may change without warnings!>
+
+
+=item B<cutoff>
+
+If set, the query will be cut off after the matches.
+
+B<This parameter is directly forwarded to the API and may not be supported in the future.>
+
+
+=item B<count>
+
+If set, the query will be only return the given number of matches,
+in case the API supports it. Will fallback to the default number of matches defined
+by the API or the backend.
+
+B<This parameter is directly forwarded to the API and may not be supported in the future.>
+
+
+=item B<p>
+
+If set, the query will page to the given number of pages in the result set.
+Will default to 1.
+
+B<This parameter is directly forwarded to the API and may not be supported in the future.>
+
+=item B<o>
+
+If set, the matches will offset to the given match in the result set.
+Will default to 0.
+
+B<This parameter is directly forwarded to the API and may not be supported in the future.>
+
+=item B<context>
+
+The context of the snippets to retrieve. Defaults to C<40-t,40-t>.
+
+B<This parameter is directly forwarded to the API and may not be supported in the future.>
+
+=item B<cq>
+
+The corpus query to limit the search to.
+
+=back
+
+
+=head2 corpus
+
+  /corpus?cq=corpusSigle+%3D+%22GOE%22
+
+Returns statistics information for a virtual corpus.
+
+=head2 text
+
+  /corpus/:corpus_id/:doc_id/:text_id
+
+Returns meta data information for a specific text.
+
+
+=head2 match
+
+  /corpus/:corpus_id/:doc_id/:text_id/:match_id?foundry=*
+
+Returns information to a match either as a C<JSON> or an C<HTML> document.
+The path defines the concrete match, by corpus identifier, document identifier,
+text identifier (all information as given by DeReKo), and match identifier
+(essentially the position of the match in the document, including highlight information).
+
+The following parameters are supported.
+
+
+=over 2
+
+=item B<foundry>
+
+Expects a foundry definition for retrieved information.
+If not given, returns all annotations for the match.
+If given, returns only given layer information for the defined foundry.
+
+B<This parameter is experimental and may change without warnings!>
+
+
+=item B<layer>
+
+Expects a layer definition for retrieved information.
+If not given, returns all annotations for the foundry.
+If given, returns only given layer information for the defined foundry.
+
+B<This parameter is experimental and may change without warnings!>
+
+
+=item B<spans>
+
+Boolean value - either C<true> or C<false> - indicating, whether span information
+(i.e. for tree structures) should be retrieved.
+
+=back
+
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2015-2018, L<IDS Mannheim|http://www.ids-mannheim.de/>
+Author: L<Nils Diewald|http://nils-diewald.de/>
+
+Kalamar is developed as part of the L<KorAP|http://korap.ids-mannheim.de/>
+Corpus Analysis Platform at the
+L<Institute for the German Language (IDS)|http://ids-mannheim.de/>,
+member of the
+L<Leibniz-Gemeinschaft|http://www.leibniz-gemeinschaft.de/en/about-us/leibniz-competition/projekte-2011/2011-funding-line-2/>
+and supported by the L<KobRA|http://www.kobra.tu-dortmund.de> project,
+funded by the
+L<Federal Ministry of Education and Research (BMBF)|http://www.bmbf.de/en/>.
+
+Kalamar is free software published under the
+L<BSD-2 License|https://raw.githubusercontent.com/KorAP/Kalamar/master/LICENSE>.
+
+=cut
diff --git a/t/fixtures/fake_backend.pl b/t/fixtures/fake_backend.pl
index 9f06697..7506d84 100644
--- a/t/fixtures/fake_backend.pl
+++ b/t/fixtures/fake_backend.pl
@@ -114,6 +114,24 @@
   return 1;
 };
 
+# Textinfo fixtures
+get '/corpus/:corpusId/:docId/:textId' => sub {
+  my $c = shift;
+
+  my $file = join('_', (
+    'textinfo',
+    $c->stash('corpusId'),
+    $c->stash('docId'),
+    $c->stash('textId')
+  ));
+
+  my $slug = slugify($file);
+
+  # Get response based on query parameter
+  my $response = $c->load_response($slug);
+  return $c->render(%$response);
+};
+
 
 # Matchinfo fixtures
 get '/corpus/:corpusId/:docId/:textId/:matchId/matchInfo' => sub {
diff --git a/t/fixtures/response_textinfo_goe_agi_00000.json b/t/fixtures/response_textinfo_goe_agi_00000.json
new file mode 100644
index 0000000..384df8d
--- /dev/null
+++ b/t/fixtures/response_textinfo_goe_agi_00000.json
@@ -0,0 +1,4 @@
+{
+  "status" : 200,
+  "json" : {"messages":[["Response format is temporary"]],"@context":"http://korap.ids-mannheim.de/ns/KoralQuery/v0.3/context.jsonld","meta":{},"document":{"@type":"koral:document","fields":[{"@type":"koral:field","type":"type:string","key":"textSigle","value":"GOE/AGI/00000"},{"@type":"koral:field","type":"type:text","key":"author","value":"Goethe, Johann Wolfgang von"},{"@type":"koral:field","type":"type:string","key":"docSigle","value":"GOE/AGI"},{"@type":"koral:field","type":"type:text","key":"docTitle","value":"Goethe: Autobiographische Schriften III, (1813-1816, 1819-1829)"},{"@type":"koral:field","type":"type:string","key":"textType","value":"Autobiographie"},{"@type":"koral:field","type":"type:string","key":"language","value":"de"},{"@type":"koral:field","type":"type:string","key":"availability","value":"ACA-NC"},{"@type":"koral:field","type":"type:text","key":"title","value":"Italienische Reise"},{"@type":"koral:field","type":"type:date","key":"creationDate","value":"1813"},{"@type":"koral:field","type":"type:string","key":"foundries","value":"corenlp corenlp/constituency corenlp/morpho corenlp/sentences dereko dereko/structure dereko/structure/base-sentences-paragraphs-pagebreaks malt malt/dependency marmot marmot/morpho opennlp opennlp/morpho opennlp/sentences treetagger treetagger/morpho"},{"@type":"koral:field","type":"type:date","key":"pubDate","value":"1982"},{"@type":"koral:field","type":"type:store","key":"reference","value":"Goethe, Johann Wolfgang von: Italienische Reise. Auch ich in Arkadien!, (Geschrieben: 1813-1816), In: Goethe, Johann Wolfgang von: Goethes Werke, Bd. 11, Autobiographische Schriften III, Hrsg.: Trunz, Erich. München: Verlag C. H. Beck, 1982, S. 9-349"},{"@type":"koral:field","type":"type:text","key":"subTitle","value":"Auch ich in Arkadien!"},{"@type":"koral:field","type":"type:store","key":"tokenSource","value":"base#tokens"},{"@type":"koral:field","type":"type:store","key":"publisher","value":"Verlag C. H. Beck"},{"@type":"koral:field","type":"type:text","key":"corpusAuthor","value":"Goethe, Johann Wolfgang von"},{"@type":"koral:field","type":"type:store","key":"layerInfos","value":"corenlp/c=spans corenlp/p=tokens corenlp/s=spans dereko/s=spans malt/d=rels marmot/m=tokens marmot/p=tokens opennlp/p=tokens opennlp/s=spans tt/l=tokens tt/p=tokens"},{"@type":"koral:field","type":"type:string","key":"pubPlace","value":"München"},{"@type":"koral:field","type":"type:text","key":"corpusTitle","value":"Goethes Werke"},{"@type":"koral:field","type":"type:string","key":"corpusSigle","value":"GOE"},{"@type":"koral:field","type":"type:store","key":"corpusEditor","value":"Trunz, Erich"}]}}
+}
diff --git a/t/fixtures/response_textinfo_goe_agy_00000.json b/t/fixtures/response_textinfo_goe_agy_00000.json
new file mode 100644
index 0000000..9182e24
--- /dev/null
+++ b/t/fixtures/response_textinfo_goe_agy_00000.json
@@ -0,0 +1,4 @@
+{
+  "status" : 200,
+  "json" : {"errors":[[630,"Document not found"]],"messages":[["Response format is temporary"]],"@context":"http://korap.ids-mannheim.de/ns/KoralQuery/v0.3/context.jsonld","meta":{},"document":{"@type":"koral:document","fields":[]}}
+}
diff --git a/t/text_info.t b/t/text_info.t
new file mode 100644
index 0000000..7c06bf6
--- /dev/null
+++ b/t/text_info.t
@@ -0,0 +1,42 @@
+use Mojo::Base -strict;
+use Test::Mojo;
+use Test::More;
+use Mojo::File qw/path/;
+
+
+#####################
+# Start Fake server #
+#####################
+my $mount_point = '/api/';
+$ENV{KALAMAR_API} = $mount_point;
+
+my $t = Test::Mojo->new('Kalamar');
+
+# Mount fake backend
+# Get the fixture path
+my $fixtures_path = path(Mojo::File->new(__FILE__)->dirname, 'fixtures');
+my $fake_backend = $t->app->plugin(
+  Mount => {
+    $mount_point =>
+      $fixtures_path->child('fake_backend.pl')
+  }
+);
+# Configure fake backend
+$fake_backend->pattern->defaults->{app}->log($t->app->log);
+
+# Query passed
+$t->get_ok('/corpus/GOE/AGI/00000')
+  ->status_is(200)
+  ->json_is('/document/fields/0/key', 'textSigle')
+  ->json_is('/document/fields/0/value', 'GOE/AGI/00000')
+  ;
+
+# Not found - should probably be 404
+$t->get_ok('/corpus/GOE/AGY/00000')
+  ->status_is(200)
+  ->json_is('/notifications/0/1', '630: Document not found')
+  ;
+
+
+done_testing;
+__END__