marketplace: Install plugins

Change-Id: I05f0d2b92c3da8aeaebd8c6db08ff8659feeeaa7
diff --git a/dev/scss/main/main.scss b/dev/scss/main/main.scss
index bf6e2dc..e8c062d 100644
--- a/dev/scss/main/main.scss
+++ b/dev/scss/main/main.scss
@@ -16,6 +16,7 @@
 @import "oauth";       // OAuth Management styles
 @import "news";        // View for optional news
 @import "plugin";      // Rules for embedded plugins
+@import "marketplace"; // Plugin Marketplace styles
 // @import "tagger";   // Tagger
 
 @import "../util";
diff --git a/dev/scss/main/marketplace.scss b/dev/scss/main/marketplace.scss
new file mode 100644
index 0000000..05e1a1a
--- /dev/null
+++ b/dev/scss/main/marketplace.scss
@@ -0,0 +1,53 @@
+@import "../base/base";
+
+/*
+* Styles for marketplace
+*/
+
+
+ul.plugin-list, ul.plugin_in-list {
+  list-style-type: none;
+  padding-left: 1.5em;
+
+  li.plugin {
+
+    margin-bottom: 2.5em;
+
+    span.client-name::before {
+      margin-left: -1.5em;
+    }
+
+    p.plugin-desc{
+      font-weight: normal;
+      font-size: 95%;
+      margin-top: .2em;
+      margin-bottom: .2em;
+    }
+    
+    p.plugin-url, p.registration_date, p.registered_by, p.inst_date {
+      font-weight: normal;
+      font-size: 80%;
+      margin-top: .2em;
+      margin-bottom: .3em;
+    }
+  }
+}
+
+
+.mkplace {
+  input[type=submit]{
+      display:          inline-block;
+      font-size: 80%;
+      margin-top: 0;
+      width:        20%;
+      min-width:    20em;
+      padding:      $base-padding;
+      cursor:           pointer;
+      border-width:     thin;
+      text-align:       center;
+    }
+  
+  a.form-button:hover {
+      color: inherit !important;
+    }
+  }
\ No newline at end of file
diff --git a/lib/Kalamar/Plugin/Auth.pm b/lib/Kalamar/Plugin/Auth.pm
index 160c533..52d2560 100644
--- a/lib/Kalamar/Plugin/Auth.pm
+++ b/lib/Kalamar/Plugin/Auth.pm
@@ -113,9 +113,13 @@
           oauthSettings => 'OAuth',
           #for marketplace settings
           marketplace => 'Marktplatz',
-          mp_regby => "Registriert von",
-          mp_regdate => "Registrierungsdatum",
-
+          plugins => 'Plugins',
+          instplugins => 'Bereits installierte Plugins',
+          regby => 'Registriert von',
+          regdate => 'Registrierungsdatum',
+          instdate=> 'Installationsdatum',
+          install => 'Installieren',
+          installFail => 'Plugin konnte nicht installiert werden',
           oauthUnregister => {
             -long => 'Möchten sie <span class="client-name"><%= $client_name %></span> wirklich löschen?',
             short => 'Löschen'
@@ -177,8 +181,14 @@
           oauthSettings => 'OAuth',
           #for marketplace settings
           marketplace => 'Marketplace',
-          mp_regby =>"Registered by",
-          mp_regdate =>"Registration date",
+          plugins => 'Plugins',
+          instplugins => 'Installed Plugins',
+          regby =>'Registered by',
+          regdate =>'Date of Registration',
+          instdate =>'Date of Installation',
+          install => 'Install',
+          uninstall => 'Uninstall',
+          installFail => 'Plugin could not be installed',
           oauthUnregister => {
             -long => 'Do you really want to unregister <span class="client-name"><%= $client_name %></span>?',
             short => 'Unregister'
@@ -972,34 +982,31 @@
       $app->loc('Auth_oauthSettings'), 'oauth'
     )
     );
-    #$app->navi->add(settings => (
-    #  $app->loc('Auth_marketplace'), 'marketplace'
-    #));
+    
+  #  $app->navi->add(settings => (
+  #    $app->loc('Auth_marketplace'), 'marketplace'
+  #  ));
 
 
-    # Lists all permitted registered plugins
+    # Helper: Returns lists of registered plugins (of all users), which are permitted
     $app->helper(
-    'auth.plugin_list_m' => sub {
-
+    'auth.plugin_list_p' => sub {
       my $c = shift;
-      state $r_url = Mojo::URL->new($c->korap->api)->path('plugins');
-      return $c->korap_request(post => $r_url, {} => form => {
+      state $l_url = Mojo::URL->new($c->korap->api)->path('plugins');
+      return $c->korap_request(post => $l_url, {} => form => {
         super_client_id => $client_id,
         super_client_secret => $client_secret,
         #list only permitted plugins
         permitted_only => 'true'
       })->then( 
           sub {
-          my $tx = shift;
-          my $json = $tx->result->json;
-
-          # Response is fine
-          if ($tx->res->is_success) {
+            my $tx = shift;
+            my $json = $tx->result->json;
+            # Response is fine
+            if ($tx->res->is_success) {
             return Mojo::Promise->resolve($json);
           };
-
-          $c->log->error($c->dumper($tx->res->to_string));
-
+            $c->log->error($tx->res->to_string);
           # Failure
           $c->notify(error => $c->loc('Auth_responseError'));
           return Mojo::Promise->reject($json // 'No response');
@@ -1008,39 +1015,139 @@
      }
    );
 
-    # Route to marketplace settings
-    $r->get('/settings/marketplace')->to(
-      cb => sub {
-      my $c = shift;
-      _set_no_cache($c->res->headers);
+ 
+    #Helper: Returns list of all plugins, which are already installed
+    $app->helper(
+      'auth.plugin_listin_p' => sub {
+        my $c = shift;
+        state $i_url = Mojo::URL->new($c->korap->api)->path('plugins/installed');
+        return $c->korap_request(post => $i_url, {} => form => {
+          super_client_id => $client_id,
+          super_client_secret => $client_secret,
+          })->then( 
+            sub {
+              my $tx = shift;
+              my $json = $tx->result->json;
+              # Response is fine
+              if ($tx->res->is_success) {  
+                return Mojo::Promise->resolve($json);
+          };
+          
+          $c->log->error($tx->res->to_string);
 
-      unless ($c->auth->token) {
-      #TODO: Handle authorization (forward to Login for example)
-        return $c->render(
+          # Failure
+          $c->notify(error => $c->loc('Auth_responseError'));
+          return Mojo::Promise->reject($json // 'No response');
+        }
+      );
+      }
+    );
+
+    # Route to marketplace (for installation and deinstallation of plugins)
+    $r->get('/settings/marketplace')->to( 
+      cb => sub {
+        my $c = shift; 
+        _set_no_cache($c->res->headers);
+        unless ($c->auth->token) {
+          return $c->render(
             template => 'exception',
             msg => $c->loc('Auth_authenticationFail'),
             status => 401
           );
         };
 
-      $c->render_later;
-      $c->auth->plugin_list_m->then(
-        sub {
-          $c->stash('plugin_list' => shift);
+      $c->render_later;      
+      my $promiselist = $c->auth->plugin_list_p;
+      my $promiseinlist = $c->auth->plugin_listin_p;
+
+      Mojo::Promise->all($promiselist, $promiseinlist)-> then(
+        sub { 
+          my ($promiselist, $promiseinlist) = @_;
+          my $plist = ref($promiselist->[0]) eq 'ARRAY' ? $promiselist->[0] : [];
+          my $plinlist = ref($promiseinlist->[0]) eq 'ARRAY' ? $promiseinlist->[0] : [];
+          my $clean_pllist = $plist;
+          $c->stash('pluginsin_list', $plinlist);
+          if($plinlist){
+            foreach my $entry (@$plinlist){
+              @$clean_pllist = grep{!($_->{client_id} eq $entry->{client_id})} @$clean_pllist ;
+              }
+            }
+            $c->stash('plugin_list', $clean_pllist);
           }
-        )->catch(
-         sub {
-            return;
-          }
-       )->finally(
-         sub {
-           return $c->render(template => 'auth/marketplace');
+          )
+          ->catch(
+            sub {
+              return;
+              }
+              )
+              ->finally(
+                sub {
+                  return $c->render(template => 'auth/marketplace');
+                  }
+              )->wait; 
          }
-       );
-      }
      )->name('marketplace');
 
 
+   # Route to install plugin
+    $r->post('/settings/marketplace')->to(
+      cb => sub {
+        my $c = shift;
+        _set_no_cache($c->res->headers);
+        my $v = $c->validation;
+        $v->required('client-id');
+        
+        if ($v->has_error) {
+          return $c->render(
+          json => [],
+          status => 400
+          );
+        };
+        
+        unless ($c->auth->token) {
+          return $c->render(
+          content => 'Unauthorized',
+          status => 401
+          );
+        };
+
+        my $mclient_id = $v->param('client-id');
+        $c->render_later;
+
+        state $p_url = Mojo::URL->new($c->korap->api)->path('plugins/install');
+        
+        return $c->korap_request(post => $p_url, {} => form => {
+          super_client_id => $client_id,
+          super_client_secret => $client_secret,
+          client_id => $mclient_id
+          })->then( 
+          sub {
+            my $tx = shift;
+            my $json = $tx->result->json;
+            # Response is fine
+            if ($tx->res->is_success) {
+              return Mojo::Promise->resolve($json);
+              };
+            #Log errors
+            $c->log->error($tx->res->to_string);
+            # Failure
+            return Mojo::Promise->reject;
+            }
+            )
+        
+         ->catch(
+            sub {
+              $c->notify('error' => $c->loc('Auth_installFail'));
+            }
+            )
+         ->finally(
+            sub {
+              return $c->redirect_to('marketplace');
+            }
+        );
+      }
+    )->name('install-plugin');
+
 
     # Route to OAuth settings
     $r->get('/settings/oauth')->to(
diff --git a/lib/Kalamar/Plugin/Auth/templates/auth/marketplace.html.ep b/lib/Kalamar/Plugin/Auth/templates/auth/marketplace.html.ep
index 75c1252..0acce2d 100644
--- a/lib/Kalamar/Plugin/Auth/templates/auth/marketplace.html.ep
+++ b/lib/Kalamar/Plugin/Auth/templates/auth/marketplace.html.ep
@@ -2,26 +2,50 @@
 
 %= page_title
 
+% my $pluginsin = stash("pluginsin_list");
+  % if (@$pluginsin) {
+  <ul class="plugin_in-list">
+    %foreach (@$pluginsin) {
+    <li class="plugin">
+      <span class="client-name" %><%= $_->{name} %></span>
+      %if ($_->{description}) {
+      <p class="plugin-desc"><%=$_->{description}%></p>
+      % };
+      % if ($_->{url}) {
+      <p class="plugin-url"> <a href="<%= $_->{url} %>"> <%= $_->{url} %></a></p>
+      % }
+      %if ($_->{installed_date}) {
+        <p class="inst_date"><%=loc('Auth_instdate')%>: <%= $_->{installed_date} %></p>
+      % }
+    </li>
+    %};  
+  </ul>
+% };
+
+
 
 % my $plugins = stash('plugin_list');
 
-% if ($plugins) {
+
+% if (@$plugins) {
 <ul class="plugin-list">
-  %foreach (@$plugins) {
-    <li class="plugin">
-      <span class="client-name client-type-<%= lc($_->{'client_type'} // 'PUBLIC') %>"><%=$_->{client_name} %></span>
-      %if ($_->{client_description}) {
-        <p class="plugin-desc"><%= $_->{client_description} %></p>
-        % };
-      %if ($_->{client_url}) {
-        <p class="plugin-url"><a href="<%= $_->{client_url} %>"><%= $_->{client_url} %></a></p>
-         %}
-      %if ($_->{registration_date}) {
-        <p class="registration_date"> <%=loc('Auth_mp_regdate')%>: <%= $_->{registration_date} %></p>
-        %};
-      %if ($_->{registered_by}) {
-        <p class="registered_by"> <%=loc('Auth_mp_regby')%>: <%= $_->{registered_by} %></p>
-        %};
-      %}
-  </ul>
-  %};
\ No newline at end of file
+%   foreach (@$plugins) {
+  <li class="plugin">
+    <span class="client-name client-type-<%= lc($_->{'client_type'} // 'PUBLIC') %>"><%=$_->{client_name} %></span>
+    % if ($_->{client_description}) {
+      <p class="plugin-desc"><%= $_->{client_description} %></p>
+    % };
+    % if ($_->{client_url}) {
+    <p class="plugin-url"><a href="<%= $_->{client_url} %>"><%= $_->{client_url} %></a></p>
+    % }
+  %if ($_->{registration_date}) {
+    <p class="registration_date"> <%=loc('Auth_regdate')%>: <%= $_->{registration_date} %></p>
+%  };
+   %= form_for 'install-plugin', class => 'mkplace', method => "POST",  begin
+   %= hidden_field 'client-id' => $_->{client_id} 
+   <input type="submit" class="form-submit" value="<%= loc('Auth_install')%>"/>
+% end
+</li>
+%};
+</ul>
+% };
diff --git a/t/plugin/auth-oauth.t b/t/plugin/auth-oauth.t
index 2c661df..16b0f6e 100644
--- a/t/plugin/auth-oauth.t
+++ b/t/plugin/auth-oauth.t
@@ -497,12 +497,24 @@
   ->header_is('Pragma','no-cache')
   ;
 
+my $loglines = '';
+$t->app->log->on(
+  message => sub {
+    my ($log, $level, @lines) = @_;
+    if ($level eq 'warn') {
+      $loglines = join ',', @lines;
+    };
+  });
 
 $t->get_ok('/settings/marketplace')
   ->status_is(200)
   ->text_is('html head title' => 'Marketplace')
+  ->element_exists_not('ul.plugin_list')
+  ->element_exists_not('ul.plugin_in-list')
   ;
 
+is($loglines, '', 'Check log is fine');
+
 $csrf = $t->post_ok('/settings/oauth/register' => form => {
   name => 'MyApp',
   type => 'PUBLIC',
@@ -921,15 +933,14 @@
 
 $fake_backend_app->add_plugin({
 "source" => {"key1" => 'wert1', "key2" => 'wert2'},
-"client_id" => "52abc",
-"permitted" => 'true',
 "client_id" => '52abc',
+"permitted" => 'true',
 "client_name" => 'Plugin 1',
 "client_type" => 'CONFIDENTIAL',
-"client_description" =>"Description Plugin 1",
-"client_url" => "http://example.client.de",
-"registration_date" => "2022-05-31T14:30:09+02:00[Europe/Berlin]",
-"registered_by" => "system"
+"client_description" => 'Description Plugin 1',
+"client_url" => 'http://example.client.de',
+"registration_date" => '2022-05-31T14:30:09+02:00[Europe/Berlin]',
+"registered_by" => 'system'
 });
 
 
@@ -984,11 +995,12 @@
   ->element_exists('ul.plugin-list')
   ->element_exists('ul.plugin-list > li')
   ->element_exists('p.registration_date')
-  ->element_exists('p.registered_by')
   ->text_is('span.client-name','Plugin 1')
   ->text_is('p.plugin-desc','Description Plugin 1')
   ;
 
+
+
 $fake_backend_app->add_plugin({
 "source" => {"one" => '1', "two" => '2'},
 "permitted" => 'false',
@@ -998,15 +1010,6 @@
 "client_description" =>'Description Plugin 2'
 });
 
-$fake_backend_app->add_plugin({
-"source" => {"answer" => '42', "hello" => 'world'},
-"permitted" => 'true',
-"client_id" => '54abc',
-"client_name" => 'Plugin 3',
-"client_type" => 'CONFIDENTIAL',
-"client_description" =>'Description Plugin 3'
-});
-
 $t->get_ok('/settings/marketplace')
   ->status_is(200)
   ->element_exists('ul.plugin-list')
@@ -1014,12 +1017,47 @@
   ->text_is('span.client-name','Plugin 1')
   ->text_is('p.plugin-desc','Description Plugin 1')
   ->element_exists('ul.plugin-list > li + li')
-  ->text_isnt('ul.plugin-list > li + li >span.client-name','Plugin 2')
-  ->text_isnt('ul.plugin-list > li + li >p.plugin-desc','Description Plugin 2')
-  ->text_is('ul.plugin-list > li + li >span.client-name','Plugin 3')
-  ->text_is('ul.plugin-list > li + li >p.plugin-desc','Description Plugin 3')
+  ->text_is('ul.plugin-list > li + li >span.client-name','Plugin 2')
+  ->text_is('ul.plugin-list > li + li >p.plugin-desc','Description Plugin 2')
   ;
 
+$t->ua->max_redirects(0);
+
+$t->post_ok('/settings/marketplace', form => {'client-id' => '52abc'})
+  ->status_is(302)
+  ->header_is(location => '/settings/marketplace')
+  ;
+
+$t->ua->max_redirects(1);
+
+$t->post_ok('/settings/marketplace', form => {'client-id' => '52abc'})
+  ->status_is(200)
+  ->element_exists('ul.plugin-list')
+  ->element_exists('ul.plugin-list > li')
+  ->text_is('ul.plugin-list > li > span.client-name','Plugin 2')
+  ->text_is('ul.plugin-list > li > p.plugin-desc','Description Plugin 2')
+  ->element_exists_not('ul.plugin-list > li + li')
+  ->element_exists('ul.plugin_in-list')
+  ->element_exists('ul.plugin_in-list > li')
+  ->text_is('ul.plugin_in-list > li > span.client-name','Plugin 1')
+  ->text_is('ul.plugin_in-list > li > p.inst_date','Date of Installation: 2022-12-13T16:33:27.621+01:00[Europe/Berlin]')
+  ;
+
+$t->ua->max_redirects(0);
+
+ $t->post_ok('/settings/marketplace', form => {'client-id' => 'unsinn31'})
+  ->status_is(302)
+  ->header_is(location => '/settings/marketplace')
+  ;
+
+$t->ua->max_redirects(1);
+
+$t->post_ok('/settings/marketplace', form => {'client-id' => 'unsinn31'})
+  ->status_is(200)
+  ->text_is('div.notify-error', 'Plugin could not be installed')
+  ;
+
+$t->ua->max_redirects(0);
 
 $t->get_ok(Mojo::URL->new('/settings/oauth/authorize')->query({
   client_id => 'xyz',
diff --git a/t/server/mock.pl b/t/server/mock.pl
index 7331c81..e76f8c5 100644
--- a/t/server/mock.pl
+++ b/t/server/mock.pl
@@ -65,6 +65,14 @@
   push @$pl_list, $cplugin;
 };
 
+helper 'add_instplugin' => sub {
+   my $c = shift;
+   my $cplugin = shift;
+   my $pl_list = $c->app->defaults('oauth.pluginin_list');
+   push @$pl_list, $cplugin; 
+};
+
+
 # Load fixture responses
 helper 'load_response' => sub {
   my $c = shift;
@@ -97,7 +105,7 @@
 
 app->defaults('oauth.client_list' => []);
 app->defaults('oauth.plugin_list' => []);
-
+app->defaults('oauth.pluginin_list' => []);
 
 # Base page
 get '/v1.0/' => sub {
@@ -560,12 +568,29 @@
   });
 };
 
-# List plugins
+# Mock API list plugins
 post '/v1.0/plugins' => sub {
   my $c = shift;
-
   my $v = $c->validation;
+  $v->required('super_client_id');
+  $v->required('super_client_secret');
+  if ($v->has_error) {
+    return $c->render(
+      json => [],
+      status => 400
+    );
+  };
 
+  return $c->render(
+   json => $c->stash('oauth.plugin_list'),
+   status => 200
+  );
+};
+
+# Mock API list installed plugins
+post '/v1.0/plugins/installed' => sub {
+  my $c = shift;
+  my $v = $c->validation;
   $v->required('super_client_id');
   $v->required('super_client_secret');
 
@@ -575,33 +600,56 @@
       status => 400
     );
   };
-  
-  my $p;
-  if($c->param("permitted_only")){
-    $p = $c->param("permitted_only");
-  }
-  else{
-    $p="false";
-  }
 
-  #Mocks the return only of permitted plugins
-  if($p eq "true"){
-    my @p_plugin_list = grep{$_->{permitted} eq "true"}  @{$c->stash('oauth.plugin_list')};
-    my $listref = \@p_plugin_list;
+  return $c->render(
+    json => $c->stash('oauth.pluginin_list'),
+    status => 200
+  );
+};
+
+
+# Mock API plugin installation
+post '/v1.0/plugins/install' => sub {
+  my $c = shift;
+  my $v = $c->validation;
+  $v->required('super_client_id');
+  $v->required('super_client_secret');
+  $v->required('client_id');
+  my $cl_id = $c->param('client_id');
+  if ($v->has_error) {
     return $c->render(
-      json => $listref,
+      json => [],
+      status => 400
+    );
+  };
+  
+  my $date = "2022-12-13T16:33:27.621+01:00[Europe/Berlin]";
+  my $pl_list =  $c->app->defaults('oauth.plugin_list');
+  my $cl_name = (grep{($_->{client_id} eq $cl_id)}@$pl_list)[0]->{client_name};
+  
+  if (length $cl_name){
+    
+    my %inst_plugin = (
+      "name" => $cl_name,
+      "client_id" => $cl_id,
+      "installed_date" => $date,
+      );
+
+    $c->add_instplugin(\%inst_plugin);
+ 
+    return $c->render(
+      json => %inst_plugin,
       status => 200
     );
   }
-  else{
-  return $c->render(
-   json => $c->stash('oauth.plugin_list'),
-    status => 200
-  );
-  }
 
+  return $c->render(
+    json => [],
+    status => 400
+  );
 };
 
+
 # Register a client
 post '/v1.0/oauth2/client/list' => sub {
   my $c = shift;