Simplify initial project setup (#12)

* Simplify initial project setup

* Added a validation for environment variables
* Added support for feature flags to simplify the initial project setup

* Add configuration service to test

* Optimize data gathering and exchange rate calculation (#14)

* Clean up changelog
This commit is contained in:
Thomas
2021-04-18 19:06:54 +02:00
committed by GitHub
parent fed10c7110
commit 5d1f1b452a
43 changed files with 310 additions and 161 deletions

View File

@@ -2,6 +2,7 @@
<gf-header
class="position-fixed px-2 w-100"
[currentRoute]="currentRoute"
[info]="info"
[user]="user"
></gf-header>
</header>

View File

@@ -7,8 +7,8 @@ import {
} from '@angular/core';
import { NavigationEnd, Router } from '@angular/router';
import { MaterialCssVarsService } from 'angular-material-css-vars';
import { InfoItem } from 'apps/api/src/app/info/interfaces/info-item.interface';
import { User } from 'apps/api/src/app/user/interfaces/user.interface';
import { formatDistanceToNow } from 'date-fns';
import { primaryColorHex, secondaryColorHex } from 'libs/helper/src';
import { hasPermission, permissions } from 'libs/helper/src';
import { Subject } from 'rxjs';
@@ -28,8 +28,8 @@ export class AppComponent implements OnDestroy, OnInit {
public canCreateAccount: boolean;
public currentRoute: string;
public currentYear = new Date().getFullYear();
public info: InfoItem;
public isLoggedIn = false;
public lastDataGathering: string;
public user: User;
public version = environment.version;
@@ -47,10 +47,8 @@ export class AppComponent implements OnDestroy, OnInit {
}
public ngOnInit() {
this.dataService.fetchInfo().subscribe(({ lastDataGathering }) => {
this.lastDataGathering = lastDataGathering
? formatDistanceToNow(new Date(lastDataGathering), { addSuffix: true })
: '';
this.dataService.fetchInfo().subscribe((info) => {
this.info = info;
});
this.router.events

View File

@@ -6,6 +6,7 @@ import {
} from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { Router } from '@angular/router';
import { InfoItem } from 'apps/api/src/app/info/interfaces/info-item.interface';
import { User } from 'apps/api/src/app/user/interfaces/user.interface';
import { hasPermission, permissions } from 'libs/helper/src';
import { EMPTY, Subject } from 'rxjs';
@@ -24,9 +25,11 @@ import { TokenStorageService } from '../../services/token-storage.service';
})
export class HeaderComponent implements OnChanges {
@Input() currentRoute: string;
@Input() info: InfoItem;
@Input() user: User;
public canAccessAdminAccessControl: boolean;
public hasPermissionToUseSocialLogin: boolean;
public impersonationId: string;
private unsubscribeSubject = new Subject<void>();
@@ -52,6 +55,11 @@ export class HeaderComponent implements OnChanges {
permissions.accessAdminControl
);
}
this.hasPermissionToUseSocialLogin = hasPermission(
this.info?.globalPermissions,
permissions.useSocialLogin
);
}
public impersonateAccount(aId: string) {
@@ -72,7 +80,10 @@ export class HeaderComponent implements OnChanges {
public openLoginDialog(): void {
const dialogRef = this.dialog.open(LoginWithAccessTokenDialog, {
autoFocus: false,
data: { accessToken: '' },
data: {
accessToken: '',
hasPermissionToUseSocialLogin: this.hasPermissionToUseSocialLogin
},
width: '30rem'
});

View File

@@ -19,7 +19,7 @@
></gf-line-chart>
</div>
<div class="container p-0">
<div *ngIf="data.fearAndGreedIndex" class="container p-0">
<gf-fear-and-greed-index
class="d-flex flex-column justify-content-center"
[fearAndGreedIndex]="data.fearAndGreedIndex"

View File

@@ -83,11 +83,17 @@ export class AdminPageComponent implements OnInit {
}
public onGatherMax() {
this.adminService.gatherMax().subscribe(() => {
setTimeout(() => {
window.location.reload();
}, 300);
});
const confirmation = confirm(
'This action may take some time. Do you want to proceed?'
);
if (confirmation === true) {
this.adminService.gatherMax().subscribe(() => {
setTimeout(() => {
window.location.reload();
}, 300);
});
}
}
public formatDistanceToNow(aDateString: string) {

View File

@@ -34,11 +34,16 @@
(click)="onFlushCache()"
>
<ion-icon class="mr-1" name="close-circle-outline"></ion-icon>
<span i18n>Reset</span>
<span i18n>Reset Data Gathering</span>
</button>
<button color="warn" mat-flat-button (click)="onGatherMax()">
<button
color="warn"
mat-flat-button
[disabled]="dataGatheringInProgress"
(click)="onGatherMax()"
>
<ion-icon class="mr-1" name="warning-outline"></ion-icon>
<span i18n>Gather Max</span>
<span i18n>Gather All Data</span>
</button>
</div>
</div>

View File

@@ -39,6 +39,7 @@ export class HomePageComponent implements OnDestroy, OnInit {
public deviceType: string;
public fearAndGreedIndex: number;
public hasImpersonationId: boolean;
public hasPermissionToAccessFearAndGreedIndex: boolean;
public hasPermissionToReadForeignPortfolio: boolean;
public hasPositions = false;
public historicalDataItems: LineChartItem[];
@@ -80,6 +81,10 @@ export class HomePageComponent implements OnDestroy, OnInit {
.subscribe(() => {
this.dataService.fetchUser().subscribe((user) => {
this.user = user;
this.hasPermissionToAccessFearAndGreedIndex = hasPermission(
user.permissions,
permissions.accessFearAndGreedIndex
);
this.hasPermissionToReadForeignPortfolio = hasPermission(
user.permissions,
permissions.readForeignPortfolio
@@ -175,14 +180,16 @@ export class HomePageComponent implements OnDestroy, OnInit {
this.cd.markForCheck();
});
this.dataService
.fetchSymbolItem('GF.FEAR_AND_GREED_INDEX')
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe(({ marketPrice }) => {
this.fearAndGreedIndex = marketPrice;
if (this.hasPermissionToAccessFearAndGreedIndex) {
this.dataService
.fetchSymbolItem('GF.FEAR_AND_GREED_INDEX')
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe(({ marketPrice }) => {
this.fearAndGreedIndex = marketPrice;
this.cd.markForCheck();
});
this.cd.markForCheck();
});
}
this.cd.markForCheck();
}

View File

@@ -1,6 +1,7 @@
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { Router } from '@angular/router';
import { hasPermission, permissions } from '@ghostfolio/helper';
import { format } from 'date-fns';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
@@ -276,7 +277,10 @@ export class LoginPageComponent implements OnDestroy, OnInit {
authToken: string
): void {
const dialogRef = this.dialog.open(ShowAccessTokenDialog, {
data: { accessToken, authToken },
data: {
accessToken,
authToken
},
disableClose: true,
width: '30rem'
});

View File

@@ -1,8 +1,6 @@
import { ChangeDetectionStrategy, Component, Inject } from '@angular/core';
import { MAT_DIALOG_DATA } from '@angular/material/dialog';
import { DataService } from '../../../services/data.service';
@Component({
selector: 'login-with-access-token-dialog',
changeDetection: ChangeDetectionStrategy.OnPush,

View File

@@ -1,13 +1,15 @@
<h1 mat-dialog-title i18n>Sign in</h1>
<div mat-dialog-content>
<div>
<div class="text-center">
<a color="accent" href="/api/auth/google" mat-flat-button
><ion-icon class="mr-1" name="logo-google"></ion-icon
><span i18n>Sign in with Google</span></a
>
</div>
<div class="my-3 text-center text-muted" i18n>or</div>
<ng-container *ngIf="data.hasPermissionToUseSocialLogin">
<div class="text-center">
<a color="accent" href="/api/auth/google" mat-flat-button
><ion-icon class="mr-1" name="logo-google"></ion-icon
><span i18n>Sign in with Google</span></a
>
</div>
<div class="my-3 text-center text-muted" i18n>or</div>
</ng-container>
<mat-form-field appearance="outline" class="w-100">
<mat-label i18n>Security Token</mat-label>
<textarea

View File

@@ -1,10 +1,5 @@
import { ChangeDetectorRef, Component, OnInit } from '@angular/core';
import { resolveFearAndGreedIndex } from 'libs/helper/src';
import { Component, OnInit } from '@angular/core';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { DataService } from '../../services/data.service';
import { TokenStorageService } from '../../services/token-storage.service';
@Component({
selector: 'gf-resources-page',
@@ -12,41 +7,17 @@ import { TokenStorageService } from '../../services/token-storage.service';
styleUrls: ['./resources-page.scss']
})
export class ResourcesPageComponent implements OnInit {
public currentFearAndGreedIndex: number;
public currentFearAndGreedIndexAsText: string;
public isLoggedIn: boolean;
private unsubscribeSubject = new Subject<void>();
/**
* @constructor
*/
public constructor(
private cd: ChangeDetectorRef,
private dataService: DataService,
private tokenStorageService: TokenStorageService
) {}
public constructor() {}
/**
* Initializes the controller
*/
public ngOnInit() {
this.isLoggedIn = !!this.tokenStorageService.getToken();
if (this.isLoggedIn) {
this.dataService
.fetchSymbolItem('GF.FEAR_AND_GREED_INDEX')
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe(({ marketPrice }) => {
this.currentFearAndGreedIndex = marketPrice;
this.currentFearAndGreedIndexAsText = resolveFearAndGreedIndex(
this.currentFearAndGreedIndex
).text;
this.cd.markForCheck();
});
}
}
public ngOnInit() {}
public ngOnDestroy() {
this.unsubscribeSubject.next();

View File

@@ -7,16 +7,8 @@
<h4 class="mb-3">Market</h4>
<div class="mb-5">
<div class="mb-4 media">
<!--<img src="" class="mr-3" />-->
<div class="media-body">
<h5 class="mt-0">
Fear & Greed Index<br class="d-block d-sm-none" />
<small *ngIf="currentFearAndGreedIndex">
(currently
<strong>{{ currentFearAndGreedIndexAsText }}</strong> at
<strong>{{ currentFearAndGreedIndex }}</strong>/100)</small
>
</h5>
<h5 class="mt-0">Fear & Greed Index</h5>
<div class="mb-1">
The fear and greed index was developed by <i>CNNMoney</i> to
measure the primary emotions (fear and greed) that influence
@@ -32,7 +24,6 @@
</div>
</div>
<div class="media">
<!--<img src="" class="mr-3" />-->
<div class="media-body">
<h5 class="mt-0">Inflation Chart</h5>
<div class="mb-1">