user.service.ts 9.35 KB
Newer Older
Lukas Burgey's avatar
Lukas Burgey committed
1
2
import { Injectable } from '@angular/core';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
Lukas Burgey's avatar
Lukas Burgey committed
3
import { throwError as observableThrowError,  Observable, BehaviorSubject, of, EMPTY } from 'rxjs';
4
import { map, catchError } from 'rxjs/operators';
Lukas Burgey's avatar
Lukas Burgey committed
5

Lukas Burgey's avatar
Lukas Burgey committed
6
7
8
9
10
11
import { CookieService } from 'ngx-cookie-service';
import { StompConfig, StompRService } from '@stomp/ng2-stompjs';
import { Message } from '@stomp/stompjs';

import { SnackBarService } from './snackbar.service';
import { IdpService } from './idp.service';
Lukas Burgey's avatar
Lukas Burgey committed
12

Lukas Burgey's avatar
Lukas Burgey committed
13
14
15
import {
  VO, User, Update, State, Deployment, DeploymentState, SSHKey, NewSSHKey, IdP, Service
} from './types/types.module';
Lukas Burgey's avatar
Lukas Burgey committed
16

Lukas Burgey's avatar
Lukas Burgey committed
17
18
19

@Injectable()
export class UserService {
20
21
  private initialized = false;
  private loggedIn: boolean = false;
Lukas Burgey's avatar
Lukas Burgey committed
22

23
  private user: User;
Lukas Burgey's avatar
Lukas Burgey committed
24
  private user$: BehaviorSubject<User>;
Lukas Burgey's avatar
Lukas Burgey committed
25

26
  private sshKeys: SSHKey[] = new Array<SSHKey>();
Lukas Burgey's avatar
Lukas Burgey committed
27
  private sshKeys$ = new BehaviorSubject<SSHKey[]>([]);
Lukas Burgey's avatar
Lukas Burgey committed
28

29
  private deployments: Map<number, Deployment> = new Map([]);
Lukas Burgey's avatar
Lukas Burgey committed
30
  private deployments$ = new BehaviorSubject<Deployment[]>([]);
Lukas Burgey's avatar
Lukas Burgey committed
31

32
  public voSelector = (user: User) => user ? user.vos : [];
33
  public serviceSelector = (user: User) => user ? user.services ? user.services : [] : [];
Lukas Burgey's avatar
Lukas Burgey committed
34
35

  constructor(
Lukas Burgey's avatar
Lukas Burgey committed
36
37
38
39
    private cookieService: CookieService,
    private http: HttpClient,
    private snackBar: SnackBarService,
    private idpService: IdpService,
Lukas Burgey's avatar
Lukas Burgey committed
40
    private stompService: StompRService,
Lukas Burgey's avatar
Lukas Burgey committed
41
  ) {
Lukas Burgey's avatar
Lukas Burgey committed
42
    this.user$ = new BehaviorSubject(null);
43
    this.connect();
44
45
  }

46
47
  // PRIVATE API
  private connect(): void {
Lukas Burgey's avatar
Lukas Burgey committed
48
49
    this.fetch();

50
51
    this.subscribeUser().subscribe(
      (newUser: User) => {
52
        console.log('user$:', newUser);
Lukas Burgey's avatar
Lukas Burgey committed
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77

        if (newUser == undefined || newUser == null) {
          // LOGGED OUT
          // show the logout to the user
          if (this.loggedIn) {
            this.snackBar.open('Logged out');

            // purge all values
            this.stompService.disconnect();
            this.user = undefined;
            this.deployments = new Map([]);
            this.deployments$.next([]);
            this.sshKeys = [];
            this.sshKeys$.next([]);
          }


          this.loggedIn =  false;
        } else {
          // LOGGED IN
          // show the login to the user
          if (!this.loggedIn) {
            this.snackBar.open('Logged in');
          }

78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94

          if (newUser.id) {
            this.connectLiveUpdates(newUser.id);
          }
          if (newUser.ssh_keys) {
            this.sshKeys = newUser.ssh_keys;
            this.sshKeys$.next(this.sshKeys);
          }
          if (newUser.deployments) {
            newUser.deployments.forEach(
              (newDep: Deployment) => {
                this.deployments.set(newDep.id,  newDep);
              }
            );
            this.deployments$.next(Array.from(this.deployments.values()));
          }

Lukas Burgey's avatar
Lukas Burgey committed
95
          this.loggedIn = true;
96
97
98
          this.initialized = true;
        }
      },
Lukas Burgey's avatar
Lukas Burgey committed
99
100
      this.handleError(true),
      () => console.log('user$ is complete'),
101
    );
Lukas Burgey's avatar
Lukas Burgey committed
102
103
  }

104
  private connectLiveUpdates(userID: number): void {
Lukas Burgey's avatar
Lukas Burgey committed
105
    // handle with care
106
    let login = userID
Lukas Burgey's avatar
Lukas Burgey committed
107
    let passcode = this.cookieService.get('sessionid');
Lukas Burgey's avatar
Lukas Burgey committed
108

Lukas Burgey's avatar
Lukas Burgey committed
109
110
    const stompConfig: StompConfig = {
      // Which server?
111
      url: 'wss://'+window.location.host+'/ws',
112

Lukas Burgey's avatar
Lukas Burgey committed
113
114
115
116
117
118
      // Headers
      // Typical keys: login, passcode, host
      headers: {
        login: 'webpage-client:' + login,
        passcode: passcode,
      },
Lukas Burgey's avatar
Lukas Burgey committed
119

Lukas Burgey's avatar
Lukas Burgey committed
120
121
122
123
124
125
      // How often to heartbeat?
      // Interval in milliseconds, set to 0 to disable
      heartbeat_in: 0, // Typical value 0 - disabled
      heartbeat_out: 20000, // Typical value 20000 - every 20 seconds
      // Wait in milliseconds before attempting auto reconnect
      // Set to 0 to disable
126
127
      // Typical value 15000 (15 seconds)
      reconnect_delay: 15000,
128

Lukas Burgey's avatar
Lukas Burgey committed
129
130
131
      // Will log diagnostics on console
      debug: false,
    };
Lukas Burgey's avatar
Lukas Burgey committed
132
133
    this.stompService.config = stompConfig;
    this.stompService.initAndConnect();
Lukas Burgey's avatar
Lukas Burgey committed
134

Lukas Burgey's avatar
Lukas Burgey committed
135
    let subscription = this.stompService.subscribe(
136
      '/exchange/users/' + userID.toString()
Lukas Burgey's avatar
Lukas Burgey committed
137
    );
138

Lukas Burgey's avatar
Lukas Burgey committed
139
140
    subscription.subscribe(
      (message: Message) => {
141
142
143
        let update: Update = JSON.parse(message.body);
        console.log('update:', update);

Lukas Burgey's avatar
Lukas Burgey committed
144
145
        if (update.error && update.error != '') {
          this.snackBar.open(update.error);
Lukas Burgey's avatar
Lukas Burgey committed
146
        }
147
148
149

        if (update.deployment) {
          this.updateDeployment(update.deployment);
Lukas Burgey's avatar
Lukas Burgey committed
150
        }
Lukas Burgey's avatar
Lukas Burgey committed
151
      },
152
      this.logErrorAndFetch,
Lukas Burgey's avatar
Lukas Burgey committed
153
    );
Lukas Burgey's avatar
Lukas Burgey committed
154
  }
155

Lukas Burgey's avatar
Lukas Burgey committed
156
157
158
159
160
161
  private updateDeployment(dep: Deployment): void {
    if (dep) {
      this.deployments.set(dep.id, dep);
      this.deployments$.next(Array.from(this.deployments.values()));
    }
  }
Lukas Burgey's avatar
Lukas Burgey committed
162

Lukas Burgey's avatar
Lukas Burgey committed
163
  private updateState(update: State): void {
164
165
166
    if (update) {
      // report an occured error
      if (update.error) {
167
        this.snackBar.open(update.error);
168
      }
Lukas Burgey's avatar
Lukas Burgey committed
169

Lukas Burgey's avatar
Lukas Burgey committed
170
171
      this.user = update.user;
      this.user$.next(this.user);
172
173

    } else {
Lukas Burgey's avatar
Lukas Burgey committed
174
      this.user$.next(undefined);
Lukas Burgey's avatar
Lukas Burgey committed
175
176
177
    }
  }

178
  private fetch(): void {
Lukas Burgey's avatar
Lukas Burgey committed
179
    this.http.get<State>('/backend/api/state').subscribe(
180
      (state: State) => this.updateState(state),
Lukas Burgey's avatar
Lukas Burgey committed
181
      this.handleError(false, 'Error fetching state. Try again later'),
Lukas Burgey's avatar
Lukas Burgey committed
182
183
184
    );
  }

Lukas Burgey's avatar
Lukas Burgey committed
185
186
187
188
189
190
  private handleError(fetch: boolean, msg?: string) {
    return (error: any) => {
      if (error.status == 403) {
        this.login();
        return
      }
191

Lukas Burgey's avatar
Lukas Burgey committed
192
193
194
195
196
197
198
      if (msg) {
        this.snackBar.open(msg);
      }
      console.log('fetch:', error);
      if (fetch) {
        this.fetch();
      }
199
200
    }
  }
Lukas Burgey's avatar
Lukas Burgey committed
201

Lukas Burgey's avatar
Lukas Burgey committed
202
203
204
  private catchError(fetch: boolean, msg?: string) {
    return (error: any) => {
      this.handleError(fetch, msg)(error);
205
      return EMPTY;
Lukas Burgey's avatar
Lukas Burgey committed
206
    }
Lukas Burgey's avatar
Lukas Burgey committed
207
208
  }

Lukas Burgey's avatar
Lukas Burgey committed
209
  private logErrorAndFetch(error: any)  {
210
211
    if (error.status === 403) {
      this.login();
Lukas Burgey's avatar
Lukas Burgey committed
212
    }
213
214
215
216

    console.log(error);
    this.snackBar.open('Error');
    this.fetch();
Lukas Burgey's avatar
Lukas Burgey committed
217
    return observableThrowError(error);
Lukas Burgey's avatar
Lukas Burgey committed
218
219
  }

Lukas Burgey's avatar
Lukas Burgey committed
220
221
222
223
224
225
226
227
  // PUBLIC API
  public serviceDescription(service: Service): string {
    if (service.description != "") {
      return service.description;
    }
    return "No description";
  }

228
229
230
231
  public login(idp?: IdP): void {
    if (idp) {
      this.idpService.setIdPPreference(idp);
    }
232

233
    window.location.href = '/backend/auth/v1/request';
234
235
  }

Lukas Burgey's avatar
Lukas Burgey committed
236
  public logout(): void {
Lukas Burgey's avatar
Lukas Burgey committed
237
    this.http.post<State>('/backend/auth/v1/logout', undefined).subscribe(
238
      (state: State) => this.updateState(state),
Lukas Burgey's avatar
Lukas Burgey committed
239
240
241
      (err: HttpErrorResponse) => {
        this.updateState(undefined);
      }
242
243
244
    );
  }

245
  public sentQuestionnaire(stateItemID: number, answers: Object) {
Lukas Burgey's avatar
Lukas Burgey committed
246
247
    return this.http.post<Deployment>('/backend/api/questionnaire?id='+String(stateItemID), answers).subscribe(
      (dep: Deployment) => this.updateDeployment(dep),
248
      this.logErrorAndFetch,
Lukas Burgey's avatar
Lukas Burgey committed
249
250
251
    );
  }

Lukas Burgey's avatar
Lukas Burgey committed
252
  public deleteUser() {
Lukas Burgey's avatar
Lukas Burgey committed
253
254
255
    return this.http.delete('/backend/api/delete_user').subscribe(
      (data: {deleted: boolean}) => {
        if (data && data.deleted) {
Lukas Burgey's avatar
Lukas Burgey committed
256
          this.user$.next(undefined);
Lukas Burgey's avatar
Lukas Burgey committed
257
258
          this.snackBar.open('Deleted user from server');
        }
Lukas Burgey's avatar
Lukas Burgey committed
259
      },
260
      this.logErrorAndFetch,
Lukas Burgey's avatar
Lukas Burgey committed
261
262
    );
  }
Lukas Burgey's avatar
Lukas Burgey committed
263

Lukas Burgey's avatar
Lukas Burgey committed
264
265
  public uploadSshKey(formData: FormData): void {
    this.http.post<SSHKey>('/backend/api/sshkey', formData).subscribe(
266
      (newKey: SSHKey) => {
Lukas Burgey's avatar
Lukas Burgey committed
267
268
        this.sshKeys.push(newKey);
        this.sshKeys$.next(this.sshKeys);
269
      },
270
      this.logErrorAndFetch,
Lukas Burgey's avatar
Lukas Burgey committed
271
272
273
    );
  }

274
  public removeSshKey(key: SSHKey) {
Lukas Burgey's avatar
Lukas Burgey committed
275
    console.log('Deleting key:', key);
Lukas Burgey's avatar
Lukas Burgey committed
276
277
278
279
280
281
282
283
284
285
286
    return this.http.post('/backend/api/sshkey', {
      'type': 'remove',
      'id': key.id,
    }).subscribe(
      (data: {deleted: boolean}) => {
        if (data && data.deleted) {
          this.sshKeys = this.sshKeys.filter(
            k => key.id != k.id
          );
          this.sshKeys$.next(this.sshKeys);
        }
287
      },
288
      this.logErrorAndFetch,
Lukas Burgey's avatar
Lukas Burgey committed
289
290
    );
  }
291

292
  public changeDeployment(action: string, vo: VO): Observable<Deployment> {
293
294
    const body = {
      'type': action,
295
      'vo': vo.id,
296
    };
297
298
    return this.http.post<Deployment>('/backend/api/deployments', body).pipe(
      catchError(this.catchError(true, "Error changing deployment")),
299
300
301
302
303
    );
  }

  // DATA SERVICE API
  //
Lukas Burgey's avatar
Lukas Burgey committed
304
  public subscribeSpecific<T>(selector: (user: User) => T): Observable<T> {
305
    return this.subscribeUser().pipe(map(selector));
306
307
  }

Lukas Burgey's avatar
Lukas Burgey committed
308
309
  public subscribeSSHKeys(): Observable<SSHKey[]> {
    return this.sshKeys$.asObservable();
310
311
  }

312
313
  public subscribeUser(): Observable<User> {
    return this.user$.asObservable();
314
315
  }

316
317
  public subscribeDeployments(): Observable<Deployment[]> {
    return this.deployments$.asObservable();
318
319
  }

320
321
322
  public subscribeVOs(): Observable<VO[]> {
    let voSelector = (user: User) => user ? user.vos : [];
    return this.subscribeSpecific<VO[]>(voSelector);
323
324
  }

325
  public subscribeServiceDeployment(service: Service): Observable<Deployment> {
326
    return this.subscribeDeployments().pipe(
Lukas Burgey's avatar
Lukas Burgey committed
327
      map((deployments: Deployment[]) => {
328
329
          return deployments.find(
            (dep: Deployment) => dep.service ? dep.service.id == service.id : false
330
331
          );
        }
332
      ),
333
334
335
    );
  }

336
  public subscribeVODeployment(vo: VO): Observable<Deployment> {
337
338
339
    return this.subscribeDeployments().pipe(
      map((deployments: Deployment[]) => {
          return deployments.find(
Lukas Burgey's avatar
Lukas Burgey committed
340
            (dep: Deployment) => dep.vo ? dep.vo.id == vo.id : false
341
342
343
          );
        }
      ),
344
345
    );
  }
Lukas Burgey's avatar
Lukas Burgey committed
346
347
348
349
350

  public subscribeDeploymentStates(): Observable<DeploymentState[]> {
    let statesSelector = (user: User) => user ? user.states : [];
    return this.subscribeSpecific<DeploymentState[]>(statesSelector);
  }
Lukas Burgey's avatar
Lukas Burgey committed
351
}