Feature/refactor user service as observable store (#117)
* Implement user service as observable store * Clean up tokenStorageService usage * Update changelog
This commit is contained in:
parent
ced4519412
commit
0d6fe4a232
@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Introduced a user service implemented as an observable store (single source of truth for state)
|
||||||
|
|
||||||
## 1.7.0 - 22.05.2021
|
## 1.7.0 - 22.05.2021
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
[currentRoute]="currentRoute"
|
[currentRoute]="currentRoute"
|
||||||
[info]="info"
|
[info]="info"
|
||||||
[user]="user"
|
[user]="user"
|
||||||
|
(signOut)="onSignOut()"
|
||||||
></gf-header>
|
></gf-header>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ import { filter, takeUntil } from 'rxjs/operators';
|
|||||||
import { environment } from '../environments/environment';
|
import { environment } from '../environments/environment';
|
||||||
import { DataService } from './services/data.service';
|
import { DataService } from './services/data.service';
|
||||||
import { TokenStorageService } from './services/token-storage.service';
|
import { TokenStorageService } from './services/token-storage.service';
|
||||||
|
import { UserService } from './services/user/user.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'gf-root',
|
selector: 'gf-root',
|
||||||
@ -30,7 +31,6 @@ export class AppComponent implements OnDestroy, OnInit {
|
|||||||
public currentYear = new Date().getFullYear();
|
public currentYear = new Date().getFullYear();
|
||||||
public deviceType: string;
|
public deviceType: string;
|
||||||
public info: InfoItem;
|
public info: InfoItem;
|
||||||
public isLoggedIn = false;
|
|
||||||
public user: User;
|
public user: User;
|
||||||
public version = environment.version;
|
public version = environment.version;
|
||||||
|
|
||||||
@ -42,7 +42,8 @@ export class AppComponent implements OnDestroy, OnInit {
|
|||||||
private deviceService: DeviceDetectorService,
|
private deviceService: DeviceDetectorService,
|
||||||
private materialCssVarsService: MaterialCssVarsService,
|
private materialCssVarsService: MaterialCssVarsService,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private tokenStorageService: TokenStorageService
|
private tokenStorageService: TokenStorageService,
|
||||||
|
private userService: UserService
|
||||||
) {
|
) {
|
||||||
this.initializeTheme();
|
this.initializeTheme();
|
||||||
this.user = undefined;
|
this.user = undefined;
|
||||||
@ -64,26 +65,22 @@ export class AppComponent implements OnDestroy, OnInit {
|
|||||||
this.currentRoute = urlSegments[0].path;
|
this.currentRoute = urlSegments[0].path;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.tokenStorageService
|
this.userService.stateChanged
|
||||||
.onChangeHasToken()
|
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
.subscribe(() => {
|
.subscribe((state) => {
|
||||||
this.isLoggedIn = !!this.tokenStorageService.getToken();
|
if (state?.user) {
|
||||||
|
this.user = state.user;
|
||||||
|
|
||||||
if (this.isLoggedIn) {
|
this.canCreateAccount = hasPermission(
|
||||||
this.dataService.fetchUser().subscribe((user) => {
|
this.user.permissions,
|
||||||
this.user = user;
|
permissions.createUserAccount
|
||||||
|
);
|
||||||
this.canCreateAccount = hasPermission(
|
} else if (!this.tokenStorageService.getToken()) {
|
||||||
this.user.permissions,
|
// User has not been logged in
|
||||||
permissions.createUserAccount
|
|
||||||
);
|
|
||||||
|
|
||||||
this.cd.markForCheck();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.user = null;
|
this.user = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.cd.markForCheck();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,6 +89,13 @@ export class AppComponent implements OnDestroy, OnInit {
|
|||||||
window.location.reload();
|
window.location.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public onSignOut() {
|
||||||
|
this.tokenStorageService.signOut();
|
||||||
|
this.userService.remove();
|
||||||
|
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
|
||||||
public ngOnDestroy() {
|
public ngOnDestroy() {
|
||||||
this.unsubscribeSubject.next();
|
this.unsubscribeSubject.next();
|
||||||
this.unsubscribeSubject.complete();
|
this.unsubscribeSubject.complete();
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
import {
|
import {
|
||||||
ChangeDetectionStrategy,
|
ChangeDetectionStrategy,
|
||||||
Component,
|
Component,
|
||||||
|
EventEmitter,
|
||||||
Input,
|
Input,
|
||||||
OnChanges
|
OnChanges,
|
||||||
|
Output
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { MatDialog } from '@angular/material/dialog';
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
@ -26,6 +28,8 @@ export class HeaderComponent implements OnChanges {
|
|||||||
@Input() info: InfoItem;
|
@Input() info: InfoItem;
|
||||||
@Input() user: User;
|
@Input() user: User;
|
||||||
|
|
||||||
|
@Output() signOut = new EventEmitter<void>();
|
||||||
|
|
||||||
public hasPermissionForSocialLogin: boolean;
|
public hasPermissionForSocialLogin: boolean;
|
||||||
public hasPermissionForSubscription: boolean;
|
public hasPermissionForSubscription: boolean;
|
||||||
public hasPermissionToAccessAdminControl: boolean;
|
public hasPermissionToAccessAdminControl: boolean;
|
||||||
@ -75,8 +79,7 @@ export class HeaderComponent implements OnChanges {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public onSignOut() {
|
public onSignOut() {
|
||||||
this.tokenStorageService.signOut();
|
this.signOut.next();
|
||||||
window.location.reload();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public openLoginDialog(): void {
|
public openLoginDialog(): void {
|
||||||
|
@ -11,13 +11,15 @@ import { catchError } from 'rxjs/operators';
|
|||||||
|
|
||||||
import { DataService } from '../services/data.service';
|
import { DataService } from '../services/data.service';
|
||||||
import { SettingsStorageService } from '../services/settings-storage.service';
|
import { SettingsStorageService } from '../services/settings-storage.service';
|
||||||
|
import { UserService } from '../services/user/user.service';
|
||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class AuthGuard implements CanActivate {
|
export class AuthGuard implements CanActivate {
|
||||||
constructor(
|
constructor(
|
||||||
private dataService: DataService,
|
private dataService: DataService,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private settingsStorageService: SettingsStorageService
|
private settingsStorageService: SettingsStorageService,
|
||||||
|
private userService: UserService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
|
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
|
||||||
@ -29,8 +31,8 @@ export class AuthGuard implements CanActivate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return new Promise<boolean>((resolve) => {
|
return new Promise<boolean>((resolve) => {
|
||||||
this.dataService
|
this.userService
|
||||||
.fetchUser()
|
.get()
|
||||||
.pipe(
|
.pipe(
|
||||||
catchError(() => {
|
catchError(() => {
|
||||||
if (state.url !== '/start') {
|
if (state.url !== '/start') {
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { ChangeDetectorRef, Component, OnInit } from '@angular/core';
|
import { ChangeDetectorRef, Component, OnInit } from '@angular/core';
|
||||||
import { DataService } from '@ghostfolio/client/services/data.service';
|
import { UserService } from '@ghostfolio/client/services/user/user.service';
|
||||||
import { TokenStorageService } from '@ghostfolio/client/services/token-storage.service';
|
|
||||||
import { baseCurrency } from '@ghostfolio/common/config';
|
import { baseCurrency } from '@ghostfolio/common/config';
|
||||||
import { User } from '@ghostfolio/common/interfaces';
|
import { User } from '@ghostfolio/common/interfaces';
|
||||||
import { Subject } from 'rxjs';
|
import { Subject } from 'rxjs';
|
||||||
@ -27,27 +26,22 @@ export class AboutPageComponent implements OnInit {
|
|||||||
*/
|
*/
|
||||||
public constructor(
|
public constructor(
|
||||||
private cd: ChangeDetectorRef,
|
private cd: ChangeDetectorRef,
|
||||||
private dataService: DataService,
|
private userService: UserService
|
||||||
private tokenStorageService: TokenStorageService
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the controller
|
* Initializes the controller
|
||||||
*/
|
*/
|
||||||
public ngOnInit() {
|
public ngOnInit() {
|
||||||
this.isLoggedIn = !!this.tokenStorageService.getToken();
|
this.userService.stateChanged
|
||||||
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
|
.subscribe((state) => {
|
||||||
|
if (state?.user) {
|
||||||
|
this.user = state.user;
|
||||||
|
|
||||||
if (this.isLoggedIn)
|
this.cd.markForCheck();
|
||||||
this.tokenStorageService
|
}
|
||||||
.onChangeHasToken()
|
});
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
|
||||||
.subscribe(() => {
|
|
||||||
this.dataService.fetchUser().subscribe((user) => {
|
|
||||||
this.user = user;
|
|
||||||
|
|
||||||
this.cd.markForCheck();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ngOnDestroy() {
|
public ngOnDestroy() {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
|
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
|
||||||
import { DataService } from '@ghostfolio/client/services/data.service';
|
import { DataService } from '@ghostfolio/client/services/data.service';
|
||||||
import { TokenStorageService } from '@ghostfolio/client/services/token-storage.service';
|
import { UserService } from '@ghostfolio/client/services/user/user.service';
|
||||||
import { DEFAULT_DATE_FORMAT } from '@ghostfolio/common/config';
|
import { DEFAULT_DATE_FORMAT } from '@ghostfolio/common/config';
|
||||||
import { Access, User } from '@ghostfolio/common/interfaces';
|
import { Access, User } from '@ghostfolio/common/interfaces';
|
||||||
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
||||||
@ -30,7 +30,7 @@ export class AccountPageComponent implements OnDestroy, OnInit {
|
|||||||
public constructor(
|
public constructor(
|
||||||
private cd: ChangeDetectorRef,
|
private cd: ChangeDetectorRef,
|
||||||
private dataService: DataService,
|
private dataService: DataService,
|
||||||
private tokenStorageService: TokenStorageService
|
private userService: UserService
|
||||||
) {
|
) {
|
||||||
this.dataService
|
this.dataService
|
||||||
.fetchInfo()
|
.fetchInfo()
|
||||||
@ -44,12 +44,11 @@ export class AccountPageComponent implements OnDestroy, OnInit {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.tokenStorageService
|
this.userService.stateChanged
|
||||||
.onChangeHasToken()
|
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
.subscribe(() => {
|
.subscribe((state) => {
|
||||||
this.dataService.fetchUser().subscribe((user) => {
|
if (state?.user) {
|
||||||
this.user = user;
|
this.user = state.user;
|
||||||
|
|
||||||
this.hasPermissionToUpdateUserSettings = hasPermission(
|
this.hasPermissionToUpdateUserSettings = hasPermission(
|
||||||
this.user.permissions,
|
this.user.permissions,
|
||||||
@ -57,7 +56,7 @@ export class AccountPageComponent implements OnDestroy, OnInit {
|
|||||||
);
|
);
|
||||||
|
|
||||||
this.cd.markForCheck();
|
this.cd.markForCheck();
|
||||||
});
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,11 +77,16 @@ export class AccountPageComponent implements OnDestroy, OnInit {
|
|||||||
})
|
})
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
.subscribe(() => {
|
.subscribe(() => {
|
||||||
this.dataService.fetchUser().subscribe((user) => {
|
this.userService.remove();
|
||||||
this.user = user;
|
|
||||||
|
|
||||||
this.cd.markForCheck();
|
this.userService
|
||||||
});
|
.get()
|
||||||
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
|
.subscribe((user) => {
|
||||||
|
this.user = user;
|
||||||
|
|
||||||
|
this.cd.markForCheck();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ import { CreateAccountDto } from '@ghostfolio/api/app/account/create-account.dto
|
|||||||
import { UpdateAccountDto } from '@ghostfolio/api/app/account/update-account.dto';
|
import { UpdateAccountDto } from '@ghostfolio/api/app/account/update-account.dto';
|
||||||
import { DataService } from '@ghostfolio/client/services/data.service';
|
import { DataService } from '@ghostfolio/client/services/data.service';
|
||||||
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
|
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
|
||||||
import { TokenStorageService } from '@ghostfolio/client/services/token-storage.service';
|
import { UserService } from '@ghostfolio/client/services/user/user.service';
|
||||||
import { User } from '@ghostfolio/common/interfaces';
|
import { User } from '@ghostfolio/common/interfaces';
|
||||||
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
||||||
import { Account as AccountModel, AccountType } from '@prisma/client';
|
import { Account as AccountModel, AccountType } from '@prisma/client';
|
||||||
@ -42,7 +42,7 @@ export class AccountsPageComponent implements OnInit {
|
|||||||
private impersonationStorageService: ImpersonationStorageService,
|
private impersonationStorageService: ImpersonationStorageService,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private tokenStorageService: TokenStorageService
|
private userService: UserService
|
||||||
) {
|
) {
|
||||||
this.routeQueryParams = route.queryParams
|
this.routeQueryParams = route.queryParams
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
@ -75,23 +75,23 @@ export class AccountsPageComponent implements OnInit {
|
|||||||
this.hasImpersonationId = !!aId;
|
this.hasImpersonationId = !!aId;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.tokenStorageService
|
this.userService.stateChanged
|
||||||
.onChangeHasToken()
|
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
.subscribe(() => {
|
.subscribe((state) => {
|
||||||
this.dataService.fetchUser().subscribe((user) => {
|
if (state?.user) {
|
||||||
this.user = user;
|
this.user = state.user;
|
||||||
|
|
||||||
this.hasPermissionToCreateAccount = hasPermission(
|
this.hasPermissionToCreateAccount = hasPermission(
|
||||||
user.permissions,
|
this.user.permissions,
|
||||||
permissions.createAccount
|
permissions.createAccount
|
||||||
);
|
);
|
||||||
this.hasPermissionToDeleteAccount = hasPermission(
|
this.hasPermissionToDeleteAccount = hasPermission(
|
||||||
user.permissions,
|
this.user.permissions,
|
||||||
permissions.deleteAccount
|
permissions.deleteAccount
|
||||||
);
|
);
|
||||||
|
|
||||||
this.cd.markForCheck();
|
this.cd.markForCheck();
|
||||||
});
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.fetchAccounts();
|
this.fetchAccounts();
|
||||||
|
@ -2,7 +2,7 @@ import { ChangeDetectorRef, Component, OnInit } from '@angular/core';
|
|||||||
import { AdminService } from '@ghostfolio/client/services/admin.service';
|
import { AdminService } from '@ghostfolio/client/services/admin.service';
|
||||||
import { CacheService } from '@ghostfolio/client/services/cache.service';
|
import { CacheService } from '@ghostfolio/client/services/cache.service';
|
||||||
import { DataService } from '@ghostfolio/client/services/data.service';
|
import { DataService } from '@ghostfolio/client/services/data.service';
|
||||||
import { TokenStorageService } from '@ghostfolio/client/services/token-storage.service';
|
import { UserService } from '@ghostfolio/client/services/user/user.service';
|
||||||
import { DEFAULT_DATE_FORMAT } from '@ghostfolio/common/config';
|
import { DEFAULT_DATE_FORMAT } from '@ghostfolio/common/config';
|
||||||
import { AdminData, User } from '@ghostfolio/common/interfaces';
|
import { AdminData, User } from '@ghostfolio/common/interfaces';
|
||||||
import { formatDistanceToNow, isValid, parseISO, sub } from 'date-fns';
|
import { formatDistanceToNow, isValid, parseISO, sub } from 'date-fns';
|
||||||
@ -34,7 +34,7 @@ export class AdminPageComponent implements OnInit {
|
|||||||
private cacheService: CacheService,
|
private cacheService: CacheService,
|
||||||
private cd: ChangeDetectorRef,
|
private cd: ChangeDetectorRef,
|
||||||
private dataService: DataService,
|
private dataService: DataService,
|
||||||
private tokenStorageService: TokenStorageService
|
private userService: UserService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -43,13 +43,12 @@ export class AdminPageComponent implements OnInit {
|
|||||||
public ngOnInit() {
|
public ngOnInit() {
|
||||||
this.fetchAdminData();
|
this.fetchAdminData();
|
||||||
|
|
||||||
this.tokenStorageService
|
this.userService.stateChanged
|
||||||
.onChangeHasToken()
|
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
.subscribe(() => {
|
.subscribe((state) => {
|
||||||
this.dataService.fetchUser().subscribe((user) => {
|
if (state?.user) {
|
||||||
this.user = user;
|
this.user = state.user;
|
||||||
});
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
|
|||||||
import { ToggleOption } from '@ghostfolio/client/components/toggle/interfaces/toggle-option.type';
|
import { ToggleOption } from '@ghostfolio/client/components/toggle/interfaces/toggle-option.type';
|
||||||
import { DataService } from '@ghostfolio/client/services/data.service';
|
import { DataService } from '@ghostfolio/client/services/data.service';
|
||||||
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
|
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
|
||||||
import { TokenStorageService } from '@ghostfolio/client/services/token-storage.service';
|
import { UserService } from '@ghostfolio/client/services/user/user.service';
|
||||||
import {
|
import {
|
||||||
PortfolioItem,
|
PortfolioItem,
|
||||||
PortfolioPosition,
|
PortfolioPosition,
|
||||||
@ -44,7 +44,7 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
|
|||||||
private dataService: DataService,
|
private dataService: DataService,
|
||||||
private deviceService: DeviceDetectorService,
|
private deviceService: DeviceDetectorService,
|
||||||
private impersonationStorageService: ImpersonationStorageService,
|
private impersonationStorageService: ImpersonationStorageService,
|
||||||
private tokenStorageService: TokenStorageService
|
private userService: UserService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -79,15 +79,14 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
|
|||||||
this.cd.markForCheck();
|
this.cd.markForCheck();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.tokenStorageService
|
this.userService.stateChanged
|
||||||
.onChangeHasToken()
|
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
.subscribe(() => {
|
.subscribe((state) => {
|
||||||
this.dataService.fetchUser().subscribe((user) => {
|
if (state?.user) {
|
||||||
this.user = user;
|
this.user = state.user;
|
||||||
|
|
||||||
this.cd.markForCheck();
|
this.cd.markForCheck();
|
||||||
});
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ import {
|
|||||||
RANGE,
|
RANGE,
|
||||||
SettingsStorageService
|
SettingsStorageService
|
||||||
} from '@ghostfolio/client/services/settings-storage.service';
|
} from '@ghostfolio/client/services/settings-storage.service';
|
||||||
import { TokenStorageService } from '@ghostfolio/client/services/token-storage.service';
|
import { UserService } from '@ghostfolio/client/services/user/user.service';
|
||||||
import {
|
import {
|
||||||
PortfolioOverview,
|
PortfolioOverview,
|
||||||
PortfolioPerformance,
|
PortfolioPerformance,
|
||||||
@ -66,7 +66,7 @@ export class HomePageComponent implements OnDestroy, OnInit {
|
|||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private settingsStorageService: SettingsStorageService,
|
private settingsStorageService: SettingsStorageService,
|
||||||
private tokenStorageService: TokenStorageService
|
private userService: UserService
|
||||||
) {
|
) {
|
||||||
this.routeQueryParams = this.route.queryParams
|
this.routeQueryParams = this.route.queryParams
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
@ -76,14 +76,14 @@ export class HomePageComponent implements OnDestroy, OnInit {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.tokenStorageService
|
this.userService.stateChanged
|
||||||
.onChangeHasToken()
|
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
.subscribe(() => {
|
.subscribe((state) => {
|
||||||
this.dataService.fetchUser().subscribe((user) => {
|
if (state?.user) {
|
||||||
this.user = user;
|
this.user = state.user;
|
||||||
|
|
||||||
this.hasPermissionToAccessFearAndGreedIndex = hasPermission(
|
this.hasPermissionToAccessFearAndGreedIndex = hasPermission(
|
||||||
user.permissions,
|
this.user.permissions,
|
||||||
permissions.accessFearAndGreedIndex
|
permissions.accessFearAndGreedIndex
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -99,12 +99,12 @@ export class HomePageComponent implements OnDestroy, OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.hasPermissionToReadForeignPortfolio = hasPermission(
|
this.hasPermissionToReadForeignPortfolio = hasPermission(
|
||||||
user.permissions,
|
this.user.permissions,
|
||||||
permissions.readForeignPortfolio
|
permissions.readForeignPortfolio
|
||||||
);
|
);
|
||||||
|
|
||||||
this.cd.markForCheck();
|
this.cd.markForCheck();
|
||||||
});
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { ChangeDetectorRef, Component, OnInit } from '@angular/core';
|
import { ChangeDetectorRef, Component, OnInit } from '@angular/core';
|
||||||
import { DataService } from '@ghostfolio/client/services/data.service';
|
import { UserService } from '@ghostfolio/client/services/user/user.service';
|
||||||
import { TokenStorageService } from '@ghostfolio/client/services/token-storage.service';
|
|
||||||
import { baseCurrency } from '@ghostfolio/common/config';
|
import { baseCurrency } from '@ghostfolio/common/config';
|
||||||
import { User } from '@ghostfolio/common/interfaces';
|
import { User } from '@ghostfolio/common/interfaces';
|
||||||
import { Subject } from 'rxjs';
|
import { Subject } from 'rxjs';
|
||||||
@ -23,27 +22,22 @@ export class PricingPageComponent implements OnInit {
|
|||||||
*/
|
*/
|
||||||
public constructor(
|
public constructor(
|
||||||
private cd: ChangeDetectorRef,
|
private cd: ChangeDetectorRef,
|
||||||
private dataService: DataService,
|
private userService: UserService
|
||||||
private tokenStorageService: TokenStorageService
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the controller
|
* Initializes the controller
|
||||||
*/
|
*/
|
||||||
public ngOnInit() {
|
public ngOnInit() {
|
||||||
this.isLoggedIn = !!this.tokenStorageService.getToken();
|
this.userService.stateChanged
|
||||||
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
|
.subscribe((state) => {
|
||||||
|
if (state?.user) {
|
||||||
|
this.user = state.user;
|
||||||
|
|
||||||
if (this.isLoggedIn)
|
this.cd.markForCheck();
|
||||||
this.tokenStorageService
|
}
|
||||||
.onChangeHasToken()
|
});
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
|
||||||
.subscribe(() => {
|
|
||||||
this.dataService.fetchUser().subscribe((user) => {
|
|
||||||
this.user = user;
|
|
||||||
|
|
||||||
this.cd.markForCheck();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ngOnDestroy() {
|
public ngOnDestroy() {
|
||||||
|
@ -5,7 +5,7 @@ import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto';
|
|||||||
import { UpdateOrderDto } from '@ghostfolio/api/app/order/update-order.dto';
|
import { UpdateOrderDto } from '@ghostfolio/api/app/order/update-order.dto';
|
||||||
import { DataService } from '@ghostfolio/client/services/data.service';
|
import { DataService } from '@ghostfolio/client/services/data.service';
|
||||||
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
|
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
|
||||||
import { TokenStorageService } from '@ghostfolio/client/services/token-storage.service';
|
import { UserService } from '@ghostfolio/client/services/user/user.service';
|
||||||
import { User } from '@ghostfolio/common/interfaces';
|
import { User } from '@ghostfolio/common/interfaces';
|
||||||
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
||||||
import { Order as OrderModel } from '@prisma/client';
|
import { Order as OrderModel } from '@prisma/client';
|
||||||
@ -42,7 +42,7 @@ export class TransactionsPageComponent implements OnInit {
|
|||||||
private impersonationStorageService: ImpersonationStorageService,
|
private impersonationStorageService: ImpersonationStorageService,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private tokenStorageService: TokenStorageService
|
private userService: UserService
|
||||||
) {
|
) {
|
||||||
this.routeQueryParams = route.queryParams
|
this.routeQueryParams = route.queryParams
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
@ -75,23 +75,23 @@ export class TransactionsPageComponent implements OnInit {
|
|||||||
this.hasImpersonationId = !!aId;
|
this.hasImpersonationId = !!aId;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.tokenStorageService
|
this.userService.stateChanged
|
||||||
.onChangeHasToken()
|
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
.subscribe(() => {
|
.subscribe((state) => {
|
||||||
this.dataService.fetchUser().subscribe((user) => {
|
if (state?.user) {
|
||||||
this.user = user;
|
this.user = state.user;
|
||||||
|
|
||||||
this.hasPermissionToCreateOrder = hasPermission(
|
this.hasPermissionToCreateOrder = hasPermission(
|
||||||
user.permissions,
|
this.user.permissions,
|
||||||
permissions.createOrder
|
permissions.createOrder
|
||||||
);
|
);
|
||||||
this.hasPermissionToDeleteOrder = hasPermission(
|
this.hasPermissionToDeleteOrder = hasPermission(
|
||||||
user.permissions,
|
this.user.permissions,
|
||||||
permissions.deleteOrder
|
permissions.deleteOrder
|
||||||
);
|
);
|
||||||
|
|
||||||
this.cd.markForCheck();
|
this.cd.markForCheck();
|
||||||
});
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.fetchOrders();
|
this.fetchOrders();
|
||||||
|
@ -2,7 +2,7 @@ import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
|
|||||||
import { LineChartItem } from '@ghostfolio/client/components/line-chart/interfaces/line-chart.interface';
|
import { LineChartItem } from '@ghostfolio/client/components/line-chart/interfaces/line-chart.interface';
|
||||||
import { DataService } from '@ghostfolio/client/services/data.service';
|
import { DataService } from '@ghostfolio/client/services/data.service';
|
||||||
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
|
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
|
||||||
import { TokenStorageService } from '@ghostfolio/client/services/token-storage.service';
|
import { UserService } from '@ghostfolio/client/services/user/user.service';
|
||||||
import { PortfolioPerformance, User } from '@ghostfolio/common/interfaces';
|
import { PortfolioPerformance, User } from '@ghostfolio/common/interfaces';
|
||||||
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
||||||
import { DateRange } from '@ghostfolio/common/types';
|
import { DateRange } from '@ghostfolio/common/types';
|
||||||
@ -35,22 +35,21 @@ export class ZenPageComponent implements OnDestroy, OnInit {
|
|||||||
private dataService: DataService,
|
private dataService: DataService,
|
||||||
private deviceService: DeviceDetectorService,
|
private deviceService: DeviceDetectorService,
|
||||||
private impersonationStorageService: ImpersonationStorageService,
|
private impersonationStorageService: ImpersonationStorageService,
|
||||||
private tokenStorageService: TokenStorageService
|
private userService: UserService
|
||||||
) {
|
) {
|
||||||
this.tokenStorageService
|
this.userService.stateChanged
|
||||||
.onChangeHasToken()
|
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
.subscribe(() => {
|
.subscribe((state) => {
|
||||||
this.dataService.fetchUser().subscribe((user) => {
|
if (state?.user) {
|
||||||
this.user = user;
|
this.user = state.user;
|
||||||
|
|
||||||
this.hasPermissionToReadForeignPortfolio = hasPermission(
|
this.hasPermissionToReadForeignPortfolio = hasPermission(
|
||||||
user.permissions,
|
this.user.permissions,
|
||||||
permissions.readForeignPortfolio
|
permissions.readForeignPortfolio
|
||||||
);
|
);
|
||||||
|
|
||||||
this.cd.markForCheck();
|
this.cd.markForCheck();
|
||||||
});
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,13 +74,6 @@ export class DataService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public fetchInfo() {
|
public fetchInfo() {
|
||||||
/*
|
|
||||||
if (this.info) {
|
|
||||||
// TODO: Cache info
|
|
||||||
return of(this.info);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
return this.http.get<InfoItem>('/api/info').pipe(
|
return this.http.get<InfoItem>('/api/info').pipe(
|
||||||
map((data) => {
|
map((data) => {
|
||||||
if (
|
if (
|
||||||
@ -154,10 +147,6 @@ export class DataService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public fetchUser() {
|
|
||||||
return this.http.get<User>('/api/user');
|
|
||||||
}
|
|
||||||
|
|
||||||
public loginAnonymous(accessToken: string) {
|
public loginAnonymous(accessToken: string) {
|
||||||
return this.http.get<any>(`/api/auth/anonymous/${accessToken}`);
|
return this.http.get<any>(`/api/auth/anonymous/${accessToken}`);
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { BehaviorSubject } from 'rxjs';
|
|
||||||
|
|
||||||
const TOKEN_KEY = 'auth-token';
|
const TOKEN_KEY = 'auth-token';
|
||||||
|
|
||||||
@ -7,23 +6,15 @@ const TOKEN_KEY = 'auth-token';
|
|||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
export class TokenStorageService {
|
export class TokenStorageService {
|
||||||
private hasTokenChangeSubject = new BehaviorSubject<void>(null);
|
|
||||||
|
|
||||||
public constructor() {}
|
public constructor() {}
|
||||||
|
|
||||||
public getToken(): string {
|
public getToken(): string {
|
||||||
return window.localStorage.getItem(TOKEN_KEY);
|
return window.localStorage.getItem(TOKEN_KEY);
|
||||||
}
|
}
|
||||||
|
|
||||||
public onChangeHasToken() {
|
|
||||||
return this.hasTokenChangeSubject.asObservable();
|
|
||||||
}
|
|
||||||
|
|
||||||
public saveToken(token: string): void {
|
public saveToken(token: string): void {
|
||||||
window.localStorage.removeItem(TOKEN_KEY);
|
window.localStorage.removeItem(TOKEN_KEY);
|
||||||
window.localStorage.setItem(TOKEN_KEY, token);
|
window.localStorage.setItem(TOKEN_KEY, token);
|
||||||
|
|
||||||
this.hasTokenChangeSubject.next();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public signOut(): void {
|
public signOut(): void {
|
||||||
@ -34,7 +25,5 @@ export class TokenStorageService {
|
|||||||
if (utmSource) {
|
if (utmSource) {
|
||||||
window.localStorage.setItem('utm_source', utmSource);
|
window.localStorage.setItem('utm_source', utmSource);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.hasTokenChangeSubject.next();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
4
apps/client/src/app/services/user/user-store.actions.ts
Normal file
4
apps/client/src/app/services/user/user-store.actions.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export enum UserStoreActions {
|
||||||
|
GetUser = 'GET_USER',
|
||||||
|
RemoveUser = 'REMOVE_USER'
|
||||||
|
}
|
5
apps/client/src/app/services/user/user-store.state.ts
Normal file
5
apps/client/src/app/services/user/user-store.state.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { User } from '@ghostfolio/common/interfaces';
|
||||||
|
|
||||||
|
export interface UserStoreState {
|
||||||
|
user: User;
|
||||||
|
}
|
56
apps/client/src/app/services/user/user.service.ts
Normal file
56
apps/client/src/app/services/user/user.service.ts
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { ObservableStore } from '@codewithdan/observable-store';
|
||||||
|
import { User } from '@ghostfolio/common/interfaces';
|
||||||
|
import { of } from 'rxjs';
|
||||||
|
import { throwError } from 'rxjs';
|
||||||
|
import { catchError, map } from 'rxjs/operators';
|
||||||
|
|
||||||
|
import { UserStoreActions } from './user-store.actions';
|
||||||
|
import { UserStoreState } from './user-store.state';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class UserService extends ObservableStore<UserStoreState> {
|
||||||
|
public constructor(private http: HttpClient) {
|
||||||
|
super({ trackStateHistory: true });
|
||||||
|
|
||||||
|
this.setState({ user: undefined }, 'INIT_STATE');
|
||||||
|
}
|
||||||
|
|
||||||
|
public get() {
|
||||||
|
const state = this.getState();
|
||||||
|
|
||||||
|
if (state?.user) {
|
||||||
|
// Get from cache
|
||||||
|
return of(state.user);
|
||||||
|
} else {
|
||||||
|
// Get from endpoint
|
||||||
|
return this.fetchUser().pipe(catchError(this.handleError));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public remove() {
|
||||||
|
this.setState({ user: null }, UserStoreActions.RemoveUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
private fetchUser() {
|
||||||
|
return this.http.get<User>('/api/user').pipe(
|
||||||
|
map((user) => {
|
||||||
|
this.setState({ user }, UserStoreActions.GetUser);
|
||||||
|
return user;
|
||||||
|
}),
|
||||||
|
catchError(this.handleError)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleError(error: any) {
|
||||||
|
if (error.error instanceof Error) {
|
||||||
|
const errMessage = error.error.message;
|
||||||
|
return throwError(errMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
return throwError(error || 'Server error');
|
||||||
|
}
|
||||||
|
}
|
@ -55,6 +55,7 @@
|
|||||||
"@angular/platform-browser": "11.2.4",
|
"@angular/platform-browser": "11.2.4",
|
||||||
"@angular/platform-browser-dynamic": "11.2.4",
|
"@angular/platform-browser-dynamic": "11.2.4",
|
||||||
"@angular/router": "11.2.4",
|
"@angular/router": "11.2.4",
|
||||||
|
"@codewithdan/observable-store": "2.2.11",
|
||||||
"@nestjs/common": "7.6.5",
|
"@nestjs/common": "7.6.5",
|
||||||
"@nestjs/config": "0.6.1",
|
"@nestjs/config": "0.6.1",
|
||||||
"@nestjs/core": "7.6.5",
|
"@nestjs/core": "7.6.5",
|
||||||
|
@ -1396,6 +1396,11 @@
|
|||||||
exec-sh "^0.3.2"
|
exec-sh "^0.3.2"
|
||||||
minimist "^1.2.0"
|
minimist "^1.2.0"
|
||||||
|
|
||||||
|
"@codewithdan/observable-store@2.2.11":
|
||||||
|
version "2.2.11"
|
||||||
|
resolved "https://registry.yarnpkg.com/@codewithdan/observable-store/-/observable-store-2.2.11.tgz#f5a168e86a2fa185a50ca40a1e838aa5e5fb007d"
|
||||||
|
integrity sha512-6CfqLJUqV0SwS4yE+9vciqxHUJ6CqIptSXXzFw80MonCDoVJvCJ/xhKfs7VZqJ4jDtEu/7ILvovFtZdLg9fiAg==
|
||||||
|
|
||||||
"@ctrl/tinycolor@^2.6.0":
|
"@ctrl/tinycolor@^2.6.0":
|
||||||
version "2.6.1"
|
version "2.6.1"
|
||||||
resolved "https://registry.yarnpkg.com/@ctrl/tinycolor/-/tinycolor-2.6.1.tgz#0e78cc836a1fd997a9a22fa1c26c555411882160"
|
resolved "https://registry.yarnpkg.com/@ctrl/tinycolor/-/tinycolor-2.6.1.tgz#0e78cc836a1fd997a9a22fa1c26c555411882160"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user