import { Injectable } from '@angular/core'; import { Observable, BehaviorSubject } from 'rxjs'; import { HttpClient } from '@angular/common/http'; import { tap, catchError, share } from 'rxjs/operators'; import { OpenSlidesComponent } from 'app/openslides.component'; import { Group } from 'app/shared/models/users/group'; import { User } from '../../shared/models/users/user'; import { environment } from 'environments/environment'; /** * The operator represents the user who is using OpenSlides. * * Information is mostly redundant to user but has different purposes. * Changes in operator can be observed, directives do so on order to show * or hide certain information. * * Could extend User? * * The operator is an {@link OpenSlidesComponent}. */ @Injectable({ providedIn: 'root' }) export class OperatorService extends OpenSlidesComponent { about_me: string; comment: string; default_password: string; email: string; first_name: string; groups_id: number[]; id: number; is_active: boolean; is_committee: boolean; is_present: boolean; last_email_send: string; last_name: string; number: string; structure_level: string; title: string; username: string; logged_in: boolean; private _user: User; /** * The subject that can be observed by other instances using observing functions. */ private operatorSubject: BehaviorSubject = new BehaviorSubject(null); /** * Representation of the {@link Group}s that the operator has (in contrast the the `groups_id`-Array) * * The operator observes the dataStore (compare {@link OpenSlidesComponent} in Order to know it's groups) */ private groups: Group[] = new Array(); /** * Recreates the operator from localStorage if it's found and starts to observe the dataStore. * @param http HttpClient */ constructor(private http: HttpClient) { super(); // recreate old operator from localStorage. if (localStorage.getItem('operator')) { const oldOperator = JSON.parse(localStorage.getItem('operator')); if (Object.keys(oldOperator).length > 0) { this.storeUser(oldOperator); } } // observe the DataStore now to avoid race conditions. Ensures to // find the groups in time this.observeDataStore(); } /** * calls `/apps/users/whoami` to find out the real operator */ public whoAmI(): Observable { return this.http.get(environment.urlPrefix + '/users/whoami/').pipe( tap(whoami => { if (whoami && whoami.user) { this.storeUser(whoami.user as User); } }), catchError(this.handleError()) ); } /** * Store the user Information in the operator, the localStorage and update the Observable * @param user usually a http response that represents a user. * * Todo: Could be refractored to use the actual User Object. * Operator is older than user, so this is still a traditional JS way */ public storeUser(user: User): void { // store in file this.about_me = user.about_me; this.comment = user.comment; this.default_password = user.default_password; this.email = user.email; this.first_name = user.first_name; this.groups_id = user.groups_id; this.id = user.id; this.is_active = user.is_active; this.is_committee = user.is_committee; this.is_present = user.is_present; this.last_email_send = user.last_email_send; this.last_name = user.last_name; this.number = user.number; this.structure_level = user.structure_level; this.title = user.title; this.username = user.username; // also store in localstorrage this.updateLocalStorage(); // update mode to inform observers this.setObservable(this.getUpdateObject()); } /** * Removes all stored information about the Operator. * * The Opposite of StoreUser. Usually a `logout()`-function. * Also removes the operator from localStorrage and * updates the observable. */ public clear() { this.about_me = null; this.comment = null; this.default_password = null; this.email = null; this.first_name = null; this.groups_id = null; this.id = null; this.is_active = null; this.is_committee = null; this.is_present = null; this.last_email_send = null; this.last_name = null; this.number = null; this.structure_level = null; this.title = null; this.username = null; this.setObservable(this.getUpdateObject()); localStorage.removeItem('operator'); } /** * Saves the operator in the localStorage for easier and faster re-login * * This is a mere comfort feature, even if the operator can be recreated * it has to pass `this.whoAmI()` during page access. */ private updateLocalStorage(): void { localStorage.setItem('operator', JSON.stringify(this.getUpdateObject())); } /** * Returns the current operator. * * Used to save the operator in localStorage or inform observers. */ private getUpdateObject(): any { return { about_me: this.about_me, comment: this.comment, default_password: this.default_password, email: this.email, first_name: this.first_name, groups_id: this.groups_id, id: this.id, is_active: this.is_active, is_committee: this.is_committee, is_present: this.is_present, last_email_send: this.last_email_send, last_name: this.last_name, number: this.number, structure_level: this.structure_level, title: this.title, username: this.username, logged_in: this.logged_in }; } /** * Observe dataStore to set groups once they are loaded. * * TODO logic to remove groups / user from certain groups. Currently is is only set and was never removed */ private observeDataStore(): void { this.DS.getObservable().subscribe(newModel => { if (newModel instanceof Group) { this.addGroup(newModel); } if (newModel instanceof User && this.id === newModel.id) { this._user = newModel; } }); } /** * Read out the Groups from the DataStore by the operators 'groups_id' * * requires that the DataStore has been setup (websocket.service) * requires that the whoAmI did return a valid operator * * This is the normal behavior after a fresh login, everythin else can * be done by observers. */ public readGroupsFromStore(): void { this.DS.filter(Group, myGroup => { if (this.groups_id.includes(myGroup.id)) { this.addGroup(myGroup); } }); } /** * Returns the behaviorSubject as an observable. * * Services an components can use it to get informed when something changes in * the operator */ public getObservable() { return this.operatorSubject.asObservable(); } /** * Inform all observers about changes * @param value */ private setObservable(value) { this.operatorSubject.next(value); } /** * Getter for the (real) {@link Group}s */ public getGroups() { return this.groups; } /** * if the operator has the corresponding ID, set the group * @param newGroup potential group that the operator has. */ private addGroup(newGroup: Group): void { if (this.groups_id.includes(newGroup.id as number)) { this.groups.push(newGroup); // inform the observers about new groups (appOsPerms) this.setObservable(newGroup); } } /** * get the user that corresponds to operator. */ get user(): User { return this._user; } }