This is an automated email from the ASF dual-hosted git repository.

martin_s pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/archiva.git


The following commit(s) were added to refs/heads/master by this push:
     new fb2a7fd  Additional angular code
fb2a7fd is described below

commit fb2a7fd6439aa0813af20167af9f001cf1af588d
Author: Martin Stockhammer <[email protected]>
AuthorDate: Tue Nov 3 00:31:44 2020 +0100

    Additional angular code
---
 .../main/archiva-web/src/app/app-routing.module.ts |   1 +
 .../main/archiva-web/src/app/app.component.html    | 134 +++++++++++---------
 .../src/main/archiva-web/src/app/app.component.ts  |  34 ++++-
 .../archiva-web/src/app/model/user-info.spec.ts    |  25 ++++
 .../main/archiva-web/src/app/model/user-info.ts    |  36 ++++++
 .../app/modules/general/login/login.component.ts   |   3 +
 .../src/app/services/archiva-request.service.ts    |  92 +++++++++-----
 .../src/app/services/authentication.service.ts     | 140 ++++++++++++++-------
 .../src/app/services/user.service.spec.ts          |  34 +++++
 .../archiva-web/src/app/services/user.service.ts   | 109 ++++++++++++++++
 .../src/main/archiva-web/src/assets/i18n/de.json   |   1 +
 .../src/main/archiva-web/src/assets/i18n/en.json   |   1 +
 12 files changed, 474 insertions(+), 136 deletions(-)

diff --git 
a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/app-routing.module.ts
 
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/app-routing.module.ts
index 19a64ef..f8c9eb0 100644
--- 
a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/app-routing.module.ts
+++ 
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/app-routing.module.ts
@@ -30,6 +30,7 @@ const routes: Routes = [
   { path: 'contact', component: ContactComponent },
   { path: 'about', component: AboutComponent },
   { path: 'login', component: LoginComponent },
+  { path: 'logout', component: HomeComponent },
   { path: '**', component: NotFoundComponent }
 ];
 
diff --git 
a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/app.component.html
 
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/app.component.html
index 7e15657..f223be3 100644
--- 
a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/app.component.html
+++ 
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/app.component.html
@@ -16,70 +16,84 @@
   ~ specific language governing permissions and limitations
   ~ under the License.
 -->
-<div class="app d-flex flex-column" >
-  <header>
-    <nav class="navbar navbar-expand-md fixed-top navbar-light " 
style="background-color: #c6cbd2;">
-      <a class="navbar-brand" routerLink="/">
-        <img src="../assets/params/images/archiva_logo_40h.png" alt="ARCHIVA">
-      </a>
-      <button class="navbar-toggler" type="button" data-toggle="collapse" 
data-target="#navbarsDefault"
-        aria-controls="navbarsDefault" aria-expanded="false" 
aria-label="Toggle Navigation">
-        <span class="navbar-toggler-icon"></span>
-      </button>
-      <div class="collapse navbar-collapse" id="navbarsDefault">
-        <ul class="navbar-nav ml-auto">
-          <li class="nav-item active">
-            <a  class="nav-link" routerLink="/">
-              <i class="fas fa-home mr-1"></i>{{ 'menu.home' |translate }}
+<div class="app d-flex flex-column">
+    <header>
+        <nav class="navbar navbar-expand-md fixed-top navbar-light " 
style="background-color: #c6cbd2;">
+            <a class="navbar-brand" routerLink="/">
+                <img src="../assets/params/images/archiva_logo_40h.png" 
alt="ARCHIVA">
             </a>
-          </li>
-          <li class="nav-item active">
-            <a  class="nav-link" routerLink="/login" data-toggle="modal" 
data-target="#loginModal">
-              <i class="fas fa-user mr-1"></i>{{'menu.login' | translate}}
-            </a>
-          </li>
-          <li class="nav-item active dropdown">
-            <a class="nav-link dropdown-toggle" id="dropdown09" 
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"><span 
class="flag-icon {{langIcon()}}"></span></a>
-            <div class="dropdown-menu" aria-labelledby="dropdown09">
-              <a class="dropdown-item" href="#en" 
(click)="switchLang('en')"><span class="flag-icon flag-icon-gb"> </span>  
English</a>
-              <a class="dropdown-item" href="#de" 
(click)="switchLang('de')"><span class="flag-icon flag-icon-de"> </span>  
German</a>
+            <button class="navbar-toggler" type="button" 
data-toggle="collapse" data-target="#navbarsDefault"
+                    aria-controls="navbarsDefault" aria-expanded="false" 
aria-label="Toggle Navigation">
+                <span class="navbar-toggler-icon"></span>
+            </button>
+
+            <div class="collapse navbar-collapse" id="navbarsDefault">
+                <div class="navbar-nav ml-auto">
+                    <span *ngIf="auth.loggedIn" class="navbar-text 
border-right pr-2 mr-2">
+                      {{user.userInfo.fullName}}
+                    </span>
+                    <ul class="navbar-nav">
+                        <li class="nav-item active">
+                            <a class="nav-link" routerLink="/">
+                                <i class="fas fa-home mr-1"></i>{{ 'menu.home' 
|translate }}
+                            </a>
+                        </li>
+                        <li *ngIf="!auth.loggedIn" class="nav-item active">
+                            <a class="nav-link" routerLink="/login" 
data-toggle="modal" data-target="#loginModal">
+                                <i class="fas fa-user mr-1"></i>{{'menu.login' 
| translate}}
+                            </a>
+                        </li>
+                        <li *ngIf="auth.loggedIn" class="nav-item active">
+                            <a class="nav-link" routerLink="/logout" 
(click)="auth.logout()">
+                                <i class="fas fa-user 
mr-1"></i>{{'menu.logout' | translate}}
+                            </a>
+                        </li>
+                        <li class="nav-item active dropdown">
+                            <a class="nav-link dropdown-toggle" 
id="dropdown09" data-toggle="dropdown"
+                               aria-haspopup="true" 
aria-expanded="false"><span class="flag-icon {{langIcon()}}"></span></a>
+                            <div class="dropdown-menu" 
aria-labelledby="dropdown09">
+                                <a class="dropdown-item" href="#en" 
(click)="switchLang('en')"><span
+                                        class="flag-icon flag-icon-gb"> 
</span> English</a>
+                                <a class="dropdown-item" href="#de" 
(click)="switchLang('de')"><span
+                                        class="flag-icon flag-icon-de"> 
</span> German</a>
+                            </div>
+                        </li>
+                        <li class="nav-item active">
+                            <a class="nav-link" routerLink="/about">
+                                <i class="far fa-question-circle 
mr-1"></i>{{'menu.about' | translate}}
+                            </a>
+                        </li>
+                        <li class="nav-item active">
+                            <a class="nav-link" routerLink="/contact">
+                                <i class="fas fa-envelope mr-1"></i>{{ 
'menu.contact' | translate }}
+                            </a>
+                        </li>
+                        <li class="nav-item active">
+                            <a class="nav-link" 
href="https://github.com/apache/archiva";>
+                                <i class="fab fa-github mr-1"></i>
+                            </a>
+                        </li>
+                    </ul>
+                </div>
             </div>
-          </li>
-          <li class="nav-item active">
-            <a  class="nav-link" routerLink="/about">
-              <i class="far fa-question-circle mr-1"></i>{{'menu.about' | 
translate}}
-            </a>
-          </li>
-          <li class="nav-item active">
-            <a  class="nav-link" routerLink="/contact">
-              <i class="fas fa-envelope mr-1"></i>{{ 'menu.contact' | 
translate }}
-            </a>
-          </li>
-          <li class="nav-item active">
-            <a class="nav-link" href="https://github.com/apache/archiva";>
-              <i class="fab fa-github mr-1"></i>
-            </a>
-          </li>
-        </ul>
-      </div>
-    </nav>
-  </header>
+        </nav>
+    </header>
 
-  <main class="container-fluid flex-fill">
-    <div >
-        <router-outlet></router-outlet>
-    </div>
-  </main>
+    <main class="container-fluid flex-fill">
+        <div>
+            <router-outlet></router-outlet>
+        </div>
+    </main>
 
-  <hr />
-  <footer class="container-fluid">
-    <div class="container">
-      <div class="row">
-        <div class="col-12">
-          <p class="text-center text-black-50">&copy; 2020 - 
archiva.apache.org</p>
+    <hr/>
+    <footer class="container-fluid">
+        <div class="container">
+            <div class="row">
+                <div class="col-12">
+                    <p class="text-center text-black-50">&copy; 2020 - 
archiva.apache.org</p>
+                </div>
+            </div>
         </div>
-      </div>
-    </div>
-  </footer>
+    </footer>
 
 </div>
\ No newline at end of file
diff --git 
a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/app.component.ts
 
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/app.component.ts
index 30b0a29..3dae41b 100644
--- 
a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/app.component.ts
+++ 
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/app.component.ts
@@ -16,28 +16,33 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import { Component } from '@angular/core';
+import {Component, OnDestroy, OnInit} from '@angular/core';
 import { TranslateService } from '@ngx-translate/core';
+import { AuthenticationService } from "./services/authentication.service";
+import {UserService} from "./services/user.service";
 
 @Component({
   selector: 'app-root',
   templateUrl: './app.component.html',
   styleUrls: ['./app.component.scss']
 })
-export class AppComponent {
+export class AppComponent implements OnInit, OnDestroy{
   title = 'archiva-web';
   version = 'Angular version 10.0.2';
 
   constructor(
-      public translate: TranslateService
+      public translate: TranslateService,
+      public auth: AuthenticationService,
+      public user: UserService
   ) {
     translate.addLangs(['en', 'de']);
     translate.setDefaultLang('en');
-    translate.use('en');
   }
 
   switchLang(lang: string) {
     this.translate.use(lang);
+    this.user.userInfo.language = lang;
+    this.user.persistUserInfo();
   }
 
   langIcon() : string {
@@ -50,4 +55,25 @@ export class AppComponent {
         return "flag-icon-" + this.translate.currentLang;
     }
   }
+
+  ngOnDestroy(): void {
+    this.auth.LoginEvent.unsubscribe();
+  }
+
+
+  ngOnInit(): void {
+    let lang = this.user.userInfo.language;
+    if (lang==null) {
+      this.translate.use('en');
+    } else {
+      this.translate.use(lang);
+    }
+    // Subscribe to login event in authenticator to switch the language
+    this.auth.LoginEvent.subscribe(userInfo => {
+      if (userInfo.language != null) {
+        this.switchLang(userInfo.language);
+      }
+    })
+
+  }
 }
diff --git 
a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/user-info.spec.ts
 
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/user-info.spec.ts
new file mode 100644
index 0000000..79fadf1
--- /dev/null
+++ 
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/user-info.spec.ts
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { UserInfo } from './user-info';
+
+describe('UserInfo', () => {
+  it('should create an instance', () => {
+    expect(new UserInfo()).toBeTruthy();
+  });
+});
diff --git 
a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/user-info.ts
 
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/user-info.ts
new file mode 100644
index 0000000..6356fe2
--- /dev/null
+++ 
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/user-info.ts
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+export class UserInfo {
+    user_id:string;
+    id:string;
+    fullName:string;
+    email:string;
+    validated:boolean;
+    locked:boolean;
+    passwordChangeRequired:boolean;
+    permanent:boolean;
+    timestampAccountCreation:Date;
+    timestampLastLogin:Date;
+    timestampLastPasswordChange:Date;
+    assignedRoles:string[];
+    readOnly:boolean;
+    userManagerId:string;
+    validationToken:string;
+    language:string;
+}
diff --git 
a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/general/login/login.component.ts
 
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/general/login/login.component.ts
index 82c797b..5cdc7f5 100644
--- 
a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/general/login/login.component.ts
+++ 
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/general/login/login.component.ts
@@ -74,4 +74,7 @@ export class LoginComponent implements OnInit {
     this.loginForm.reset();
     this.authenticationService.login(customerData.userid, 
customerData.password, resultHandler);
   }
+
+
+  
 }
diff --git 
a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/archiva-request.service.ts
 
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/archiva-request.service.ts
index 2060469..9d79733 100644
--- 
a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/archiva-request.service.ts
+++ 
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/archiva-request.service.ts
@@ -16,49 +16,81 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import { Injectable } from '@angular/core';
-import {HttpClient, HttpEvent, HttpResponse} from "@angular/common/http";
-import { environment } from "../../environments/environment";
+import {Injectable} from '@angular/core';
+import {HttpClient} from "@angular/common/http";
+import {environment} from "../../environments/environment";
 import {Observable} from "rxjs";
 import {ErrorMessage} from "../model/error-message";
 import {TranslateService} from "@ngx-translate/core";
 
 @Injectable({
-  providedIn: 'root'
+    providedIn: 'root'
 })
 export class ArchivaRequestService {
 
-  constructor(private http : HttpClient, private translator : 
TranslateService) { }
+    // Stores the access token locally
+    accessToken: string;
 
-  executeRestCall<R>(type: string, module: string, service: string, input: 
object ) : Observable<R> {
-    let modulePath = environment.application.servicePaths[module];
-    let url = environment.application.baseUrl + 
environment.application.restPath + "/" + modulePath + "/" + service;
-    let token = localStorage.getItem("access_token")
-    let headers = null;
-    if (token != null) {
-      headers = {
-        "Authorization": "Bearer " + localStorage.getItem("access_token")
-      }
-    } else {
-      headers = {};
+    constructor(private http: HttpClient, private translator: 
TranslateService) {
     }
-    if (type == "get") {
-      return this.http.get<R>(url, {"headers":headers});
-    } else if ( type == "post") {
-      return this.http.post<R>(url, input, {"headers":headers});
+
+    /**
+     * Executes a rest call to the archiva / redback REST services.
+     * @param type the type of the call (get, post, update)
+     * @param module the module (archiva, redback)
+     * @param service the REST service to call
+     * @param input the input data, if this is a POST or UPDATE request
+     */
+    executeRestCall<R>(type: string, module: string, service: string, input: 
object): Observable<R> {
+        let modulePath = environment.application.servicePaths[module];
+        let url = environment.application.baseUrl + 
environment.application.restPath + "/" + modulePath + "/" + service;
+        let token = this.getToken();
+        let headers = null;
+        if (token != null) {
+            headers = {
+                "Authorization": "Bearer " + token
+            }
+        } else {
+            headers = {};
+        }
+        if (type == "get") {
+            return this.http.get<R>(url, {"headers": headers});
+        } else if (type == "post") {
+            return this.http.post<R>(url, input, {"headers": headers});
+        }
     }
-  }
 
+    public resetToken() {
+        this.accessToken = null;
+    }
+
+    private getToken(): string {
+        if (this.accessToken != null) {
+            return this.accessToken;
+        } else {
+            let token = localStorage.getItem("access_token");
+            if (token != null && token != "") {
+                this.accessToken = token;
+                return token;
+            } else {
+                return null;
+            }
+        }
+    }
 
-  translateError(errorMsg : ErrorMessage) : string {
-    if (errorMsg.errorKey!=null && errorMsg.errorKey!='') {
-      let parms = {};
-      if (errorMsg.args!=null && errorMsg.args.length>0) {
-        for ( let i=0; i<errorMsg.args.length; i++) {
-          parms['arg' + i] = errorMsg.args[i];
+    /**
+     * Translates a given error message to the current set language.
+     * @param errorMsg the errorMsg as returned by a REST call
+     */
+    public translateError(errorMsg: ErrorMessage): string {
+        if (errorMsg.errorKey != null && errorMsg.errorKey != '') {
+            let parms = {};
+            if (errorMsg.args != null && errorMsg.args.length > 0) {
+                for (let i = 0; i < errorMsg.args.length; i++) {
+                    parms['arg' + i] = errorMsg.args[i];
+                }
+            }
+            return this.translator.instant('api.' + errorMsg.errorKey, parms);
         }
-      }
-      return this.translator.instant('api.'+errorMsg.errorKey, parms);
     }
-  }
 }
diff --git 
a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/authentication.service.ts
 
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/authentication.service.ts
index c062b95..ba4cdb1 100644
--- 
a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/authentication.service.ts
+++ 
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/authentication.service.ts
@@ -16,61 +16,117 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import { Injectable } from '@angular/core';
+import {EventEmitter, Injectable} from '@angular/core';
 import {ArchivaRequestService} from "./archiva-request.service";
 import {AccessToken} from "../model/access-token";
-import { environment } from "../../environments/environment";
+import {environment} from "../../environments/environment";
 import {ErrorMessage} from "../model/error-message";
 import {ErrorResult} from "../model/error-result";
 import {HttpErrorResponse} from "@angular/common/http";
+import {UserService} from "./user.service";
+import {UserInfo} from "../model/user-info";
 
+/**
+ * The AuthenticationService handles user authentication and stores user data 
after successful login
+ */
 @Injectable({
-  providedIn: 'root'
+    providedIn: 'root'
 })
 export class AuthenticationService {
+    loggedIn: boolean;
+
+    /**
+     * The LoginEvent is emitted, when a successful login happened. And the 
corresponding user info was retrieved.
+     */
+    public LoginEvent: EventEmitter<UserInfo> = new EventEmitter<UserInfo>();
+
+
+    constructor(private rest: ArchivaRequestService,
+                private userService: UserService) {
+        this.loggedIn = false;
+        this.restoreLoginData();
+    }
+
+    private restoreLoginData() {
+        let accessToken = localStorage.getItem("access_token");
+        if (accessToken != null) {
+            let expirationDate = localStorage.getItem("token_expire");
+            if (expirationDate != null) {
+                let expDate = new Date(expirationDate);
+                let currentDate = new Date();
+                if (currentDate < expDate) {
+                    this.loggedIn = true
+                    let observer = this.userService.retrieveUserInfo();
+                    observer.subscribe(userInfo =>
+                        this.LoginEvent.emit(userInfo)
+                    );
+                }
+            }
+        }
+
+
+    }
 
-  constructor(private rest: ArchivaRequestService) { }
 
-  login(userid:string, password:string, resultHandler: (n: string, err?: 
ErrorMessage[]) => void) {
+    /**
+     * Tries to login by sending the login data to the REST service. If the 
login was successful the access
+     * and refresh token is stored locally.
+     *
+     * @param userid The user id for the login
+     * @param password The password
+     * @param resultHandler A result handler that is executed, after calling 
the login service
+     */
+    login(userid: string, password: string, resultHandler: (n: string, err?: 
ErrorMessage[]) => void) {
 
-      const data = { 'grant_type':'authorization_code',
-        'client_id':environment.application.client_id,
-        'user_id':userid, 'password':password
-      };
-      let authObserver =  
this.rest.executeRestCall<AccessToken>('post','redback', 'auth/authenticate', 
data );
-      let tokenObserver = {
-          next: (x: AccessToken) => {
-              localStorage.setItem("access_token", x.access_token);
-              localStorage.setItem("refresh_token", x.refresh_token);
-              if (x.expires_in!=null) {
-                  let dt = new Date();
-                  dt.setSeconds(dt.getSeconds() + x.expires_in);
-                  localStorage.setItem("token_expire", dt.toISOString());
-              }
-              resultHandler("OK");
-          },
-          error: ( err: HttpErrorResponse) => {
-              console.log("Error " + (JSON.stringify(err)));
-              let result = err.error as ErrorResult
-              if (result.errorMessages!=null) {
-                  for (let msg of result.errorMessages) {
-                      console.error('Observer got an error: ' + msg.errorKey)
-                  }
-                  resultHandler("ERROR", result.errorMessages);
-              } else {
-                  resultHandler("ERROR", null);
-              }
+        const data = {
+            'grant_type': 'authorization_code',
+            'client_id': environment.application.client_id,
+            'user_id': userid, 'password': password
+        };
+        let authObserver = this.rest.executeRestCall<AccessToken>('post', 
'redback', 'auth/authenticate', data);
+        let tokenObserver = {
+            next: (x: AccessToken) => {
+                localStorage.setItem("access_token", x.access_token);
+                localStorage.setItem("refresh_token", x.refresh_token);
+                if (x.expires_in != null) {
+                    let dt = new Date();
+                    dt.setSeconds(dt.getSeconds() + x.expires_in);
+                    localStorage.setItem("token_expire", dt.toISOString());
+                }
+                let userObserver = this.userService.retrieveUserInfo();
+                this.loggedIn = true;
+                userObserver.subscribe(userInfo =>
+                    this.LoginEvent.emit(userInfo));
+                resultHandler("OK");
+            },
+            error: (err: HttpErrorResponse) => {
+                console.log("Error " + (JSON.stringify(err)));
+                let result = err.error as ErrorResult
+                if (result.errorMessages != null) {
+                    for (let msg of result.errorMessages) {
+                        console.error('Observer got an error: ' + msg.errorKey)
+                    }
+                    resultHandler("ERROR", result.errorMessages);
+                } else {
+                    resultHandler("ERROR", null);
+                }
 
-          },
-          // complete: () => console.log('Observer got a complete 
notification'),
-      };
-      authObserver.subscribe(tokenObserver)
+            },
+            // complete: () => console.log('Observer got a complete 
notification'),
+        };
+        authObserver.subscribe(tokenObserver)
 
-  }
+    }
 
-  logout() {
-      localStorage.removeItem("access_token");
-      localStorage.removeItem("refresh_token");
-      localStorage.removeItem("token_expire");
-  }
+    /**
+     * Resets the stored user data
+     */
+    logout() {
+        localStorage.removeItem("access_token");
+        localStorage.removeItem("refresh_token");
+        localStorage.removeItem("token_expire");
+        this.loggedIn = false;
+        this.userService.resetUser();
+        this.rest.resetToken();
+    }
 }
diff --git 
a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/user.service.spec.ts
 
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/user.service.spec.ts
new file mode 100644
index 0000000..e21a651
--- /dev/null
+++ 
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/user.service.spec.ts
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { TestBed } from '@angular/core/testing';
+
+import { UserService } from './user.service';
+
+describe('UserService', () => {
+  let service: UserService;
+
+  beforeEach(() => {
+    TestBed.configureTestingModule({});
+    service = TestBed.inject(UserService);
+  });
+
+  it('should be created', () => {
+    expect(service).toBeTruthy();
+  });
+});
diff --git 
a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/user.service.ts
 
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/user.service.ts
new file mode 100644
index 0000000..3360e55
--- /dev/null
+++ 
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/user.service.ts
@@ -0,0 +1,109 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import {Injectable} from '@angular/core';
+import {ArchivaRequestService} from "./archiva-request.service";
+import {UserInfo} from '../model/user-info';
+import {HttpErrorResponse} from "@angular/common/http";
+import {ErrorResult} from "../model/error-result";
+import {Observable} from "rxjs";
+
+@Injectable({
+    providedIn: 'root'
+})
+export class UserService {
+
+    userInfo: UserInfo;
+
+    constructor(private rest: ArchivaRequestService) {
+        this.userInfo = new UserInfo()
+        this.loadPersistedUserInfo();
+    }
+
+    /**
+     * Retrieves the user information from the REST service for the current 
logged in user.
+     * This works only, if a valid access token is present.
+     * It returns a observable that can be subscribed to catch the user 
information.
+     */
+    public retrieveUserInfo(): Observable<UserInfo> {
+        return new Observable<UserInfo>((resultObserver) => {
+            let accessToken = localStorage.getItem("access_token");
+
+            if (accessToken != null) {
+                let infoObserver = this.rest.executeRestCall<UserInfo>("get", 
"redback", "users/me", null);
+                let userInfoObserver = {
+                    next: (x: UserInfo) => {
+                        this.userInfo = x;
+                        if (this.userInfo.language == null) {
+                            this.loadPersistedUserInfo();
+                        }
+                        this.persistUserInfo();
+                        resultObserver.next(this.userInfo);
+                    },
+                    error: (err: HttpErrorResponse) => {
+                        console.log("Error " + (JSON.stringify(err)));
+                        let result = err.error as ErrorResult
+                        if (result.errorMessages != null) {
+                            for (let msg of result.errorMessages) {
+                                console.error('Observer got an error: ' + 
msg.errorKey)
+                            }
+                        }
+                        resultObserver.error();
+                    },
+                    complete: () => {
+                        resultObserver.complete();
+                    }
+                };
+                infoObserver.subscribe(userInfoObserver);
+            }
+        });
+    }
+
+    /**
+     * Stores user information persistent. Not the complete UserInfo object, 
only properties, that
+     * are needed.
+     */
+    public persistUserInfo() {
+        if (this.userInfo != null && this.userInfo.user_id != null && 
this.userInfo.user_id != "") {
+            let prefix = "user." + this.userInfo.user_id;
+            localStorage.setItem(prefix + ".user_id", this.userInfo.user_id);
+            localStorage.setItem(prefix + ".id", this.userInfo.id);
+            if (this.userInfo.language != null && this.userInfo.language != 
"") {
+                localStorage.setItem(prefix + ".language", 
this.userInfo.language);
+            }
+        }
+    }
+
+    /**
+     * Loads the persisted user info from the local storage
+     */
+    public loadPersistedUserInfo() {
+        if (this.userInfo.user_id != null && this.userInfo.user_id != "") {
+            let prefix = "user." + this.userInfo.user_id;
+            this.userInfo.language = localStorage.getItem(prefix + 
".language");
+        }
+    }
+
+    /**
+     * Resets the user info to default values.
+     */
+    resetUser() {
+        this.userInfo = new UserInfo();
+    }
+
+}
diff --git 
a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/assets/i18n/de.json
 
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/assets/i18n/de.json
index c98ece6..12eec0c 100644
--- 
a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/assets/i18n/de.json
+++ 
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/assets/i18n/de.json
@@ -11,6 +11,7 @@
   "menu": {
     "home": "Home",
     "login": "Anmelden",
+    "logout": "Abmelden",
     "about": "Über",
     "contact": "Kontakt"
   },
diff --git 
a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/assets/i18n/en.json
 
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/assets/i18n/en.json
index 418bc20..68494c4 100644
--- 
a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/assets/i18n/en.json
+++ 
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/assets/i18n/en.json
@@ -11,6 +11,7 @@
   "menu": {
     "home": "Home",
     "login": "Login",
+    "logout": "Logout",
     "about": "About",
     "contact": "Contact"
   },

Reply via email to