Introduce Kalamar::Request

Change-Id: I0c21bf8521cebeedc95a4251d7abf074e30ca2a3
diff --git a/Changes b/Changes
index 88cc9e3..c684b1b 100755
--- a/Changes
+++ b/Changes
@@ -1,4 +1,4 @@
-0.41 2021-02-15
+0.41 2021-02-16
         - Introduce CORS headers to the proxy.
         - Introduce Content Security Policy.
         - Remove default api endpoint from config to
@@ -26,6 +26,8 @@
         - Define resources in Makefile.
         - Support Mojolicious >= 9.
         - Remove deprecated helper methods.
+        - Introduce Kalamar::Request and
+          kalamar->request helper.
 
 0.40 2020-12-17
         - Modernize ES and fix in-loops.
diff --git a/lib/Kalamar/Plugin/KalamarUser.pm b/lib/Kalamar/Plugin/KalamarUser.pm
index 04a90b2..3963fec 100644
--- a/lib/Kalamar/Plugin/KalamarUser.pm
+++ b/lib/Kalamar/Plugin/KalamarUser.pm
@@ -3,6 +3,7 @@
 use Mojo::Util qw/deprecated/;
 use Mojo::Promise;
 use Mojo::ByteStream 'b';
+use Kalamar::Request;
 
 has 'api';
 has 'ua';
@@ -10,18 +11,12 @@
 sub register {
   my ($plugin, $mojo, $param) = @_;
 
+
   # Load parameter from config file
   if (my $config_param = $mojo->config('Kalamar')) {
     $param = { %$param, %$config_param };
   };
 
-  # Load 'notifications' plugin
-  unless (exists $mojo->renderer->helpers->{notify}) {
-    $mojo->plugin(Notifications => {
-      HTML => 1
-    });
-  };
-
   # Set API!
   $plugin->api($param->{api}) or return;
   $plugin->ua(Mojo::UserAgent->new(
@@ -33,6 +28,7 @@
   # Set app to server
   $plugin->ua->server->app($mojo);
 
+
   # Get a user agent object for Kalamar
   $mojo->helper(
     'kalamar_ua' => sub {
@@ -40,6 +36,7 @@
     }
   );
 
+
   # Get user handle
   $mojo->helper(
     'user_handle' => sub {
@@ -62,6 +59,7 @@
     }
   );
 
+
   # This is a new general korap_request helper,
   # that can trigger some hooks for, e.g., authentication
   # or analysis. It returns a promise.
@@ -90,8 +88,22 @@
       return $ua->start_p($tx);
     }
   );
+
+
+  # Return KorAP-Request object helper
+  $mojo->helper(
+    'kalamar.request' => sub {
+      return Kalamar::Request->new(
+        controller => shift,
+        method => shift,
+        url => shift,
+        ua => $plugin->ua
+      )->param(@_);
+    }
+  );
 };
 
+
 1;
 
 
diff --git a/lib/Kalamar/Request.pm b/lib/Kalamar/Request.pm
new file mode 100644
index 0000000..ebe68d9
--- /dev/null
+++ b/lib/Kalamar/Request.pm
@@ -0,0 +1,82 @@
+package Kalamar::Request;
+use Mojo::Base -base;
+use Mojo::Promise;
+
+has [qw!controller ua!];
+
+has url => sub {
+  my $self = shift;
+  if (@_) {
+    $self->{url} = Mojo::URL->new(shift);
+    return $self;
+  };
+  return $self->{url};
+};
+
+has method => sub {
+  my $self = shift;
+  if (@_) {
+    $self->{method} = uc(shift);
+    return $self;
+  };
+  return $self->{method} // 'GET';
+};
+
+
+# Get or set parameters for the transaction
+sub param {
+  my $self = shift;
+  if (scalar(@_) > 0) {
+    if (defined $_[0]) {
+      $self->{param} = [@_];
+    }
+    else {
+      $self->{param} = undef;
+    }
+    return $self;
+  };
+  return $self->{param} // [];
+};
+
+
+# Start transaction and return a promise
+sub start {
+  my $self = shift;
+
+  # TODO: Better return a rejected promise
+  unless ($self->url) {
+    return Mojo::Promise->reject('No URL defined');
+  };
+
+  unless ($self->controller) {
+    return Mojo::Promise->reject('No controller defined');
+  };
+
+  unless ($self->ua) {
+    return Mojo::Promise->reject('No useragent defined');
+   };
+
+  my @param = ($self->method, $self->url);
+  push @param, @{$self->param} if scalar(@{$self->param}) > 0;
+
+  my $tx = $self->ua->build_tx(@param);
+
+  my $c = $self->controller;
+
+  # Set X-Forwarded for
+  if ($c->app->renderer->helpers->{'client_ip'}) {
+    $tx->req->headers->header(
+      'X-Forwarded-For' => $c->client_ip
+    );
+  };
+
+  # Emit Hook to alter request
+  $c->app->plugins->emit_hook(
+    before_korap_request => ($c, $tx)
+  );
+
+  # Return promise
+  return $self->ua->start_p($tx);
+};
+
+1;
diff --git a/t/request.t b/t/request.t
new file mode 100644
index 0000000..4457048
--- /dev/null
+++ b/t/request.t
@@ -0,0 +1,77 @@
+use Mojolicious::Lite;
+use Test::More tests => 17;
+use Mojolicious::Controller;
+
+use_ok('Kalamar::Request');
+
+my $req = Kalamar::Request->new(
+  url => 'http://www.example.com',
+  method => 'POST',
+  controller => Mojolicious::Controller->new
+)->param('x', 'y');
+
+is($req->method, 'POST');
+is($req->url, 'http://www.example.com');
+ok($req->controller);
+is_deeply($req->param, ['x','y'], 'Compare');
+
+my $err;
+$req->start->catch(
+  sub {
+    $err = shift
+  })->finally(
+    sub {
+      is($err,'No useragent defined','Error');
+    })->wait;
+
+my $c = $req->controller;
+$c->app(app);
+ok($req->controller(undef),'Set controller');
+
+$req->start->catch(
+  sub {
+    $err = shift
+  })->finally(
+    sub {
+      is($err,'No controller defined','Error');
+    })->wait;
+
+ok($req->url(undef),'Set URL');
+
+$req->start->catch(
+  sub {
+    $err = shift
+  })->finally(
+    sub {
+      is($err,'No URL defined','Error');
+    })->wait;
+
+put '/test' => sub {
+  shift->render(text => 'Hallo')
+};
+
+# Set UA
+ok($req->ua(Mojo::UserAgent->new));
+$req->ua->server->app->log->level('fatal');
+ok($req->controller($c));
+ok($req->method('put'));
+ok($req->url('/test'));
+ok($req->param(undef));
+
+$err = undef;
+$req->start->then(
+  sub {
+    my $tx = shift;
+    is($tx->res->body,'Hallo');
+  })->catch(
+    sub {
+      $err = shift;
+    })->finally(
+      sub {
+        ok(!$err);
+      })->wait;
+
+done_testing;
+__END__
+
+1;