Browse Source

Refactor AuthGuard, persist displayMode in user settings

pull/110/head
Thomas 4 years ago
parent
commit
bc2e905258
  1. 7
      apps/api/src/app/user/update-user-settings.dto.ts
  2. 3
      apps/api/src/app/user/user.controller.ts
  3. 16
      apps/api/src/app/user/user.service.ts
  4. 2
      apps/api/src/services/configuration.service.ts
  5. 6
      apps/client/src/app/components/header/header.component.html
  6. 56
      apps/client/src/app/core/auth.guard.ts
  7. 1
      apps/client/src/app/core/http-response.interceptor.ts
  8. 24
      apps/client/src/app/pages/account/account-page.component.ts
  9. 16
      apps/client/src/app/pages/account/account-page.html
  10. 3
      apps/client/src/app/pages/zen/zen-page.scss
  11. 2
      libs/common/src/lib/config.ts
  12. 4
      libs/common/src/lib/interfaces/user-settings.interface.ts
  13. 8
      prisma/schema.prisma

7
apps/api/src/app/user/update-user-settings.dto.ts

@ -1,7 +1,10 @@
import { Currency } from '@prisma/client'; import { Currency, DisplayMode } from '@prisma/client';
import { IsString } from 'class-validator'; import { IsString } from 'class-validator';
export class UpdateUserSettingsDto { export class UpdateUserSettingsDto {
@IsString() @IsString()
currency: Currency; baseCurrency: Currency;
@IsString()
displayMode: DisplayMode;
} }

3
apps/api/src/app/user/user.controller.ts

@ -93,7 +93,8 @@ export class UserController {
} }
return await this.userService.updateUserSettings({ return await this.userService.updateUserSettings({
currency: data.currency, currency: data.baseCurrency,
displayMode: data.displayMode,
userId: this.request.user.id userId: this.request.user.id
}); });
} }

16
apps/api/src/app/user/user.service.ts

@ -5,7 +5,7 @@ import { resetHours } from '@ghostfolio/common/helper';
import { User as IUser, UserWithSettings } from '@ghostfolio/common/interfaces'; import { User as IUser, UserWithSettings } from '@ghostfolio/common/interfaces';
import { getPermissions, permissions } from '@ghostfolio/common/permissions'; import { getPermissions, permissions } from '@ghostfolio/common/permissions';
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { Currency, Prisma, Provider, User } from '@prisma/client'; import { Currency, DisplayMode, Prisma, Provider, User } from '@prisma/client';
import { add } from 'date-fns'; import { add } from 'date-fns';
const crypto = require('crypto'); const crypto = require('crypto');
@ -52,8 +52,9 @@ export class UserService {
accounts: Account, accounts: Account,
permissions: currentPermissions, permissions: currentPermissions,
settings: { settings: {
baseCurrency: Settings?.currency || UserService.DEFAULT_CURRENCY, locale,
locale baseCurrency: Settings?.currency ?? UserService.DEFAULT_CURRENCY,
displayMode: Settings.displayMode ?? DisplayMode.DEFAULT
}, },
subscription: { subscription: {
expiresAt: resetHours(add(new Date(), { days: 7 })), expiresAt: resetHours(add(new Date(), { days: 7 })),
@ -79,6 +80,7 @@ export class UserService {
// Set default settings if needed // Set default settings if needed
user.Settings = { user.Settings = {
currency: UserService.DEFAULT_CURRENCY, currency: UserService.DEFAULT_CURRENCY,
displayMode: DisplayMode.DEFAULT,
updatedAt: new Date(), updatedAt: new Date(),
userId: user?.id userId: user?.id
}; };
@ -187,14 +189,17 @@ export class UserService {
public async updateUserSettings({ public async updateUserSettings({
currency, currency,
displayMode,
userId userId
}: { }: {
currency: Currency; currency?: Currency;
displayMode?: DisplayMode;
userId: string; userId: string;
}) { }) {
await this.prisma.settings.upsert({ await this.prisma.settings.upsert({
create: { create: {
currency, currency,
displayMode,
User: { User: {
connect: { connect: {
id: userId id: userId
@ -202,7 +207,8 @@ export class UserService {
} }
}, },
update: { update: {
currency currency,
displayMode
}, },
where: { where: {
userId: userId userId: userId

2
apps/api/src/services/configuration.service.ts

@ -1,8 +1,8 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { DataSource } from '@prisma/client';
import { bool, cleanEnv, json, num, port, str } from 'envalid'; import { bool, cleanEnv, json, num, port, str } from 'envalid';
import { Environment } from './interfaces/environment.interface'; import { Environment } from './interfaces/environment.interface';
import { DataSource } from '.prisma/client';
@Injectable() @Injectable()
export class ConfigurationService { export class ConfigurationService {

6
apps/client/src/app/components/header/header.component.html

@ -8,11 +8,14 @@
class="d-none d-sm-block" class="d-none d-sm-block"
i18n i18n
mat-flat-button mat-flat-button
[color]="currentRoute === 'home' ? 'primary' : null" [color]="
currentRoute === 'home' || currentRoute === 'zen' ? 'primary' : null
"
[routerLink]="['/']" [routerLink]="['/']"
>Overview</a >Overview</a
> >
<a <a
*ngIf="user?.settings?.displayMode === 'DEFAULT'"
class="d-none d-sm-block mx-1" class="d-none d-sm-block mx-1"
i18n i18n
mat-flat-button mat-flat-button
@ -21,6 +24,7 @@
>Analysis</a >Analysis</a
> >
<a <a
*ngIf="user?.settings?.displayMode === 'DEFAULT'"
class="d-none d-sm-block mx-1" class="d-none d-sm-block mx-1"
i18n i18n
mat-flat-button mat-flat-button

56
apps/client/src/app/core/auth.guard.ts

@ -5,16 +5,19 @@ import {
Router, Router,
RouterStateSnapshot RouterStateSnapshot
} from '@angular/router'; } from '@angular/router';
import { DisplayMode } from '@prisma/client';
import { EMPTY } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { DataService } from '../services/data.service';
import { SettingsStorageService } from '../services/settings-storage.service'; import { SettingsStorageService } from '../services/settings-storage.service';
import { TokenStorageService } from '../services/token-storage.service';
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class AuthGuard implements CanActivate { export class AuthGuard implements CanActivate {
constructor( constructor(
private dataService: DataService,
private router: Router, private router: Router,
private settingsStorageService: SettingsStorageService, private settingsStorageService: SettingsStorageService
private tokenStorageService: TokenStorageService
) {} ) {}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) { canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
@ -25,23 +28,46 @@ export class AuthGuard implements CanActivate {
); );
} }
const isLoggedIn = !!this.tokenStorageService.getToken(); return new Promise<boolean>((resolve) => {
this.dataService
if (isLoggedIn) { .fetchUser()
if (state.url === '/start') { .pipe(
this.router.navigate(['/home']); catchError(() => {
return false; if (state.url !== '/start') {
this.router.navigate(['/start']);
resolve(false);
return EMPTY;
} }
return true; resolve(true);
return EMPTY;
})
)
.subscribe((user) => {
if (
state.url === '/home' &&
user.settings.displayMode === DisplayMode.ZEN
) {
this.router.navigate(['/zen']);
resolve(false);
} else if (state.url === '/start') {
if (user.settings.displayMode === DisplayMode.ZEN) {
this.router.navigate(['/zen']);
} else {
this.router.navigate(['/home']);
} }
// Not logged in resolve(false);
if (state.url !== '/start') { } else if (
this.router.navigate(['/start']); state.url === '/zen' &&
return false; user.settings.displayMode === DisplayMode.DEFAULT
) {
this.router.navigate(['/home']);
resolve(false);
} }
return true; resolve(true);
});
});
} }
} }

1
apps/client/src/app/core/http-response.interceptor.ts

@ -79,7 +79,6 @@ export class HttpResponseInterceptor implements HttpInterceptor {
} }
} else if (error.status === StatusCodes.UNAUTHORIZED) { } else if (error.status === StatusCodes.UNAUTHORIZED) {
this.tokenStorageService.signOut(); this.tokenStorageService.signOut();
this.router.navigate(['start']);
} }
return throwError(''); return throwError('');

24
apps/client/src/app/pages/account/account-page.component.ts

@ -51,8 +51,6 @@ export class AccountPageComponent implements OnDestroy, OnInit {
this.dataService.fetchUser().subscribe((user) => { this.dataService.fetchUser().subscribe((user) => {
this.user = user; this.user = user;
this.user.settings.mode = 'ZEN';
this.hasPermissionToUpdateUserSettings = hasPermission( this.hasPermissionToUpdateUserSettings = hasPermission(
this.user.permissions, this.user.permissions,
permissions.updateUserSettings permissions.updateUserSettings
@ -70,9 +68,14 @@ export class AccountPageComponent implements OnDestroy, OnInit {
this.update(); this.update();
} }
public onChangeBaseCurrency({ value: currency }: { value: Currency }) { public onChangeUserSettings(aKey: string, aValue: string) {
const settings = { ...this.user.settings, [aKey]: aValue };
this.dataService this.dataService
.putUserSettings({ currency }) .putUserSettings({
baseCurrency: settings?.baseCurrency,
displayMode: settings?.displayMode
})
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntil(this.unsubscribeSubject))
.subscribe(() => { .subscribe(() => {
this.dataService.fetchUser().subscribe((user) => { this.dataService.fetchUser().subscribe((user) => {
@ -83,19 +86,6 @@ export class AccountPageComponent implements OnDestroy, OnInit {
}); });
} }
public onChangeMode({ value: mode }: { value: Currency }) {
/*this.dataService
.putUserSettings({ currency })
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe(() => {
this.dataService.fetchUser().subscribe((user) => {
this.user = user;
this.cd.markForCheck();
});
});*/
}
public ngOnDestroy() { public ngOnDestroy() {
this.unsubscribeSubject.next(); this.unsubscribeSubject.next();
this.unsubscribeSubject.complete(); this.unsubscribeSubject.complete();

16
apps/client/src/app/pages/account/account-page.html

@ -30,14 +30,14 @@
<div class="d-flex mt-4 py-1"> <div class="d-flex mt-4 py-1">
<div class="pt-4 w-50" i18n>Settings</div> <div class="pt-4 w-50" i18n>Settings</div>
<div class="w-50"> <div class="w-50">
<form #changeBaseCurrencyForm="ngForm"> <form #changeUserSettingsForm="ngForm">
<mat-form-field appearance="outline" class="w-100"> <mat-form-field appearance="outline" class="mb-3 w-100">
<mat-label i18n>Base Currency</mat-label> <mat-label i18n>Base Currency</mat-label>
<mat-select <mat-select
name="baseCurrency" name="baseCurrency"
[disabled]="!hasPermissionToUpdateUserSettings" [disabled]="!hasPermissionToUpdateUserSettings"
[value]="user.settings.baseCurrency" [value]="user.settings.baseCurrency"
(selectionChange)="onChangeBaseCurrency($event)" (selectionChange)="onChangeUserSettings('baseCurrency', $event.value)"
> >
<mat-option <mat-option
*ngFor="let currency of currencies" *ngFor="let currency of currencies"
@ -46,15 +46,13 @@
> >
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
</form>
<form #changeModeForm="ngForm">
<mat-form-field appearance="outline" class="w-100"> <mat-form-field appearance="outline" class="w-100">
<mat-label i18n>Mode</mat-label> <mat-label i18n>Display Mode</mat-label>
<mat-select <mat-select
name="baseCurrency" name="displayMode"
[disabled]="!hasPermissionToUpdateUserSettings" [disabled]="!hasPermissionToUpdateUserSettings"
[value]="user.settings.mode" [value]="user.settings.displayMode"
(selectionChange)="onChangeMode($event)" (selectionChange)="onChangeUserSettings('displayMode', $event.value)"
> >
<mat-option value="DEFAULT">Default</mat-option> <mat-option value="DEFAULT">Default</mat-option>
<mat-option value="ZEN">Zen</mat-option> <mat-option value="ZEN">Zen</mat-option>

3
apps/client/src/app/pages/zen/zen-page.scss

@ -4,8 +4,7 @@
.chart-container { .chart-container {
aspect-ratio: 16 / 9; aspect-ratio: 16 / 9;
cursor: pointer; margin-top: 3rem;
margin-top: -1rem;
max-height: 50vh; max-height: 50vh;
// Fallback for aspect-ratio (using padding hack) // Fallback for aspect-ratio (using padding hack)

2
libs/common/src/lib/config.ts

@ -1,4 +1,4 @@
import { Currency } from '.prisma/client'; import { Currency } from '@prisma/client';
export const baseCurrency = Currency.CHF; export const baseCurrency = Currency.CHF;

4
libs/common/src/lib/interfaces/user-settings.interface.ts

@ -1,7 +1,7 @@
import { Currency } from '@prisma/client'; import { Currency, DisplayMode } from '@prisma/client';
export interface UserSettings { export interface UserSettings {
baseCurrency: Currency; baseCurrency: Currency;
displayMode: DisplayMode;
locale: string; locale: string;
mode: 'DEFAULT' | 'ZEN';
} }

8
prisma/schema.prisma

@ -92,7 +92,8 @@ model Property {
} }
model Settings { model Settings {
currency Currency currency Currency?
displayMode DisplayMode?
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
User User @relation(fields: [userId], references: [id]) User User @relation(fields: [userId], references: [id])
userId String @id userId String @id
@ -133,6 +134,11 @@ enum DataSource {
YAHOO YAHOO
} }
enum DisplayMode {
DEFAULT
ZEN
}
enum Provider { enum Provider {
ANONYMOUS ANONYMOUS
GOOGLE GOOGLE

Loading…
Cancel
Save