From 51fbc538ca508a5e7fb2356cc42bd6c3ec430784 Mon Sep 17 00:00:00 2001 From: Thomas <4159106+dtslvr@users.noreply.github.com> Date: Sat, 17 Jul 2021 11:04:43 +0200 Subject: [PATCH] Feature/set public stripe key dynamically (#216) * Set public Stripe key dynamically * Update changelog --- CHANGELOG.md | 1 + apps/api/src/app/info/info.service.ts | 4 +++ .../api/src/services/configuration.service.ts | 1 + .../interfaces/environment.interface.ts | 1 + apps/client/src/app/app.component.ts | 9 ++--- apps/client/src/app/app.module.ts | 12 +++++-- .../app/pages/about/about-page.component.ts | 18 ++++------ .../pages/account/account-page.component.ts | 31 ++++++++--------- ...eate-or-update-account-dialog.component.ts | 14 +++----- .../pages/landing/landing-page.component.ts | 13 ++------ .../pages/pricing/pricing-page.component.ts | 11 ++----- .../pages/register/register-page.component.ts | 17 ++++------ ...-or-update-transaction-dialog.component.ts | 11 ++----- .../transactions-page.component.ts | 16 +++------ apps/client/src/app/services/data.service.ts | 24 ++++++-------- .../src/environments/environment.prod.ts | 2 +- apps/client/src/main.ts | 33 ++++++++++++++----- .../src/lib/interfaces/info-item.interface.ts | 1 + replace.build.js | 10 +----- 19 files changed, 100 insertions(+), 129 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index adcc7f3bc..b807c2404 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Improved the styling of the current pricing plan - Improved the styling of the transaction type badge +- Set the public _Stripe_ key dynamically - Upgraded `angular-material-css-vars` from version `2.0.0` to `2.1.0` ### Fixed diff --git a/apps/api/src/app/info/info.service.ts b/apps/api/src/app/info/info.service.ts index 54ee3a2c2..b41ea6be4 100644 --- a/apps/api/src/app/info/info.service.ts +++ b/apps/api/src/app/info/info.service.ts @@ -20,6 +20,7 @@ export class InfoService { ) {} public async get(): Promise { + const info: Partial = {}; const platforms = await this.prisma.platform.findMany({ orderBy: { name: 'asc' }, select: { id: true, name: true } @@ -41,9 +42,12 @@ export class InfoService { if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) { globalPermissions.push(permissions.enableSubscription); + + info.stripePublicKey = this.configurationService.get('STRIPE_PUBLIC_KEY'); } return { + ...info, globalPermissions, platforms, currencies: Object.values(Currency), diff --git a/apps/api/src/services/configuration.service.ts b/apps/api/src/services/configuration.service.ts index 1ba240f4f..909d5657d 100644 --- a/apps/api/src/services/configuration.service.ts +++ b/apps/api/src/services/configuration.service.ts @@ -30,6 +30,7 @@ export class ConfigurationService { REDIS_HOST: str({ default: 'localhost' }), REDIS_PORT: port({ default: 6379 }), ROOT_URL: str({ default: 'http://localhost:4200' }), + STRIPE_PUBLIC_KEY: str({ default: '' }), STRIPE_SECRET_KEY: str({ default: '' }), WEB_AUTH_RP_ID: host({ default: 'localhost' }) }); diff --git a/apps/api/src/services/interfaces/environment.interface.ts b/apps/api/src/services/interfaces/environment.interface.ts index 990922f25..c5d075caa 100644 --- a/apps/api/src/services/interfaces/environment.interface.ts +++ b/apps/api/src/services/interfaces/environment.interface.ts @@ -20,6 +20,7 @@ export interface Environment extends CleanedEnvAccessors { REDIS_HOST: string; REDIS_PORT: number; ROOT_URL: string; + STRIPE_PUBLIC_KEY: string; STRIPE_SECRET_KEY: string; WEB_AUTH_RP_ID: string; } diff --git a/apps/client/src/app/app.component.ts b/apps/client/src/app/app.component.ts index db0f0b869..aeec70e6b 100644 --- a/apps/client/src/app/app.component.ts +++ b/apps/client/src/app/app.component.ts @@ -56,13 +56,6 @@ export class AppComponent implements OnDestroy, OnInit { public ngOnInit() { this.deviceType = this.deviceService.getDeviceInfo().deviceType; - this.dataService - .fetchInfo() - .pipe(takeUntil(this.unsubscribeSubject)) - .subscribe((info) => { - this.info = info; - }); - this.router.events .pipe(filter((event) => event instanceof NavigationEnd)) .subscribe(() => { @@ -70,6 +63,8 @@ export class AppComponent implements OnDestroy, OnInit { const urlSegmentGroup = urlTree.root.children[PRIMARY_OUTLET]; const urlSegments = urlSegmentGroup.segments; this.currentRoute = urlSegments[0].path; + + this.info = this.dataService.fetchInfo(); }); this.userService.stateChanged diff --git a/apps/client/src/app/app.module.ts b/apps/client/src/app/app.module.ts index 6ccb38c04..3db686434 100644 --- a/apps/client/src/app/app.module.ts +++ b/apps/client/src/app/app.module.ts @@ -15,7 +15,7 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { MaterialCssVarsModule } from 'angular-material-css-vars'; import { MarkdownModule } from 'ngx-markdown'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; -import { NgxStripeModule } from 'ngx-stripe'; +import { NgxStripeModule, STRIPE_PUBLISHABLE_KEY } from 'ngx-stripe'; import { environment } from '../environments/environment'; import { CustomDateAdapter } from './adapter/custom-date-adapter'; @@ -27,6 +27,10 @@ import { authInterceptorProviders } from './core/auth.interceptor'; import { httpResponseInterceptorProviders } from './core/http-response.interceptor'; import { LanguageService } from './core/language.service'; +export function NgxStripeFactory(): string { + return environment.stripePublicKey; +} + @NgModule({ declarations: [AppComponent], imports: [ @@ -57,7 +61,11 @@ import { LanguageService } from './core/language.service'; useClass: CustomDateAdapter, deps: [LanguageService, MAT_DATE_LOCALE, Platform] }, - { provide: MAT_DATE_FORMATS, useValue: DateFormats } + { provide: MAT_DATE_FORMATS, useValue: DateFormats }, + { + provide: STRIPE_PUBLISHABLE_KEY, + useFactory: NgxStripeFactory + } ], bootstrap: [AppComponent] }) diff --git a/apps/client/src/app/pages/about/about-page.component.ts b/apps/client/src/app/pages/about/about-page.component.ts index 062550637..75d143a81 100644 --- a/apps/client/src/app/pages/about/about-page.component.ts +++ b/apps/client/src/app/pages/about/about-page.component.ts @@ -39,19 +39,13 @@ export class AboutPageComponent implements OnDestroy, OnInit { * Initializes the controller */ public ngOnInit() { - this.dataService - .fetchInfo() - .pipe(takeUntil(this.unsubscribeSubject)) - .subscribe(({ globalPermissions, statistics }) => { - this.hasPermissionForStatistics = hasPermission( - globalPermissions, - permissions.enableStatistics - ); - - this.statistics = statistics; + const { globalPermissions, statistics } = this.dataService.fetchInfo(); + this.hasPermissionForStatistics = hasPermission( + globalPermissions, + permissions.enableStatistics + ); - this.changeDetectorRef.markForCheck(); - }); + this.statistics = statistics; this.userService.stateChanged .pipe(takeUntil(this.unsubscribeSubject)) diff --git a/apps/client/src/app/pages/account/account-page.component.ts b/apps/client/src/app/pages/account/account-page.component.ts index 8dd69da0b..44bb0611c 100644 --- a/apps/client/src/app/pages/account/account-page.component.ts +++ b/apps/client/src/app/pages/account/account-page.component.ts @@ -54,24 +54,19 @@ export class AccountPageComponent implements OnDestroy, OnInit { private userService: UserService, public webAuthnService: WebAuthnService ) { - this.dataService - .fetchInfo() - .pipe(takeUntil(this.unsubscribeSubject)) - .subscribe(({ currencies, globalPermissions, subscriptions }) => { - this.coupon = subscriptions?.[0]?.coupon; - this.couponId = subscriptions?.[0]?.couponId; - this.currencies = currencies; - - this.hasPermissionForSubscription = hasPermission( - globalPermissions, - permissions.enableSubscription - ); - - this.price = subscriptions?.[0]?.price; - this.priceId = subscriptions?.[0]?.priceId; - - this.changeDetectorRef.markForCheck(); - }); + const { currencies, globalPermissions, subscriptions } = + this.dataService.fetchInfo(); + this.coupon = subscriptions?.[0]?.coupon; + this.couponId = subscriptions?.[0]?.couponId; + this.currencies = currencies; + + this.hasPermissionForSubscription = hasPermission( + globalPermissions, + permissions.enableSubscription + ); + + this.price = subscriptions?.[0]?.price; + this.priceId = subscriptions?.[0]?.priceId; this.userService.stateChanged .pipe(takeUntil(this.unsubscribeSubject)) diff --git a/apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.component.ts b/apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.component.ts index bd809dfcb..2c8ecb416 100644 --- a/apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.component.ts +++ b/apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.component.ts @@ -8,7 +8,6 @@ import { import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { Currency } from '@prisma/client'; import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; import { DataService } from '../../../services/data.service'; import { CreateOrUpdateAccountDialogParams } from './interfaces/interfaces'; @@ -34,15 +33,10 @@ export class CreateOrUpdateAccountDialog implements OnDestroy { ) {} ngOnInit() { - this.dataService - .fetchInfo() - .pipe(takeUntil(this.unsubscribeSubject)) - .subscribe(({ currencies, platforms }) => { - this.currencies = currencies; - this.platforms = platforms; - - this.changeDetectorRef.markForCheck(); - }); + const { currencies, platforms } = this.dataService.fetchInfo(); + + this.currencies = currencies; + this.platforms = platforms; } public onCancel(): void { diff --git a/apps/client/src/app/pages/landing/landing-page.component.ts b/apps/client/src/app/pages/landing/landing-page.component.ts index 29b05fb7a..e8ce6c786 100644 --- a/apps/client/src/app/pages/landing/landing-page.component.ts +++ b/apps/client/src/app/pages/landing/landing-page.component.ts @@ -3,10 +3,8 @@ import { Router } from '@angular/router'; import { LineChartItem } from '@ghostfolio/client/components/line-chart/interfaces/line-chart.interface'; import { DataService } from '@ghostfolio/client/services/data.service'; import { TokenStorageService } from '@ghostfolio/client/services/token-storage.service'; -import { WebAuthnService } from '@ghostfolio/client/services/web-authn.service'; import { format } from 'date-fns'; import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; @Component({ selector: 'gf-landing-page', @@ -34,16 +32,11 @@ export class LandingPageComponent implements OnDestroy, OnInit { * Initializes the controller */ public ngOnInit() { - this.dataService - .fetchInfo() - .pipe(takeUntil(this.unsubscribeSubject)) - .subscribe(({ demoAuthToken }) => { - this.demoAuthToken = demoAuthToken; + const { demoAuthToken } = this.dataService.fetchInfo(); - this.initializeLineChart(); + this.demoAuthToken = demoAuthToken; - this.changeDetectorRef.markForCheck(); - }); + this.initializeLineChart(); } public initializeLineChart() { diff --git a/apps/client/src/app/pages/pricing/pricing-page.component.ts b/apps/client/src/app/pages/pricing/pricing-page.component.ts index 061f2c52a..386683f0e 100644 --- a/apps/client/src/app/pages/pricing/pricing-page.component.ts +++ b/apps/client/src/app/pages/pricing/pricing-page.component.ts @@ -28,15 +28,10 @@ export class PricingPageComponent implements OnDestroy, OnInit { private dataService: DataService, private userService: UserService ) { - this.dataService - .fetchInfo() - .pipe(takeUntil(this.unsubscribeSubject)) - .subscribe(({ subscriptions }) => { - this.coupon = this.price = subscriptions?.[0]?.coupon; - this.price = subscriptions?.[0]?.price; + const { subscriptions } = this.dataService.fetchInfo(); - this.changeDetectorRef.markForCheck(); - }); + this.coupon = this.price = subscriptions?.[0]?.coupon; + this.price = subscriptions?.[0]?.price; } /** diff --git a/apps/client/src/app/pages/register/register-page.component.ts b/apps/client/src/app/pages/register/register-page.component.ts index 33758eba0..f0e02d351 100644 --- a/apps/client/src/app/pages/register/register-page.component.ts +++ b/apps/client/src/app/pages/register/register-page.component.ts @@ -41,18 +41,13 @@ export class RegisterPageComponent implements OnDestroy, OnInit { * Initializes the controller */ public ngOnInit() { - this.dataService - .fetchInfo() - .pipe(takeUntil(this.unsubscribeSubject)) - .subscribe(({ demoAuthToken, globalPermissions }) => { - this.demoAuthToken = demoAuthToken; - this.hasPermissionForSocialLogin = hasPermission( - globalPermissions, - permissions.enableSocialLogin - ); + const { demoAuthToken, globalPermissions } = this.dataService.fetchInfo(); - this.changeDetectorRef.markForCheck(); - }); + this.demoAuthToken = demoAuthToken; + this.hasPermissionForSocialLogin = hasPermission( + globalPermissions, + permissions.enableSocialLogin + ); } public async createAccount() { diff --git a/apps/client/src/app/pages/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.component.ts b/apps/client/src/app/pages/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.component.ts index ae19f8260..9507f174e 100644 --- a/apps/client/src/app/pages/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.component.ts +++ b/apps/client/src/app/pages/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.component.ts @@ -50,15 +50,10 @@ export class CreateOrUpdateTransactionDialog implements OnDestroy { ) {} ngOnInit() { - this.dataService - .fetchInfo() - .pipe(takeUntil(this.unsubscribeSubject)) - .subscribe(({ currencies, platforms }) => { - this.currencies = currencies; - this.platforms = platforms; + const { currencies, platforms } = this.dataService.fetchInfo(); - this.changeDetectorRef.markForCheck(); - }); + this.currencies = currencies; + this.platforms = platforms; this.filteredLookupItems = this.searchSymbolCtrl.valueChanges.pipe( startWith(''), diff --git a/apps/client/src/app/pages/transactions/transactions-page.component.ts b/apps/client/src/app/pages/transactions/transactions-page.component.ts index de52de282..39eefdd32 100644 --- a/apps/client/src/app/pages/transactions/transactions-page.component.ts +++ b/apps/client/src/app/pages/transactions/transactions-page.component.ts @@ -10,7 +10,6 @@ import { UserService } from '@ghostfolio/client/services/user/user.service'; import { User } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { Order as OrderModel } from '@prisma/client'; -import { environment } from 'apps/client/src/environments/environment'; import { format, parseISO } from 'date-fns'; import { DeviceDetectorService } from 'ngx-device-detector'; import { EMPTY, Subject, Subscription } from 'rxjs'; @@ -72,17 +71,12 @@ export class TransactionsPageComponent implements OnDestroy, OnInit { * Initializes the controller */ public ngOnInit() { - this.dataService - .fetchInfo() - .pipe(takeUntil(this.unsubscribeSubject)) - .subscribe(({ globalPermissions }) => { - this.hasPermissionToImportOrders = hasPermission( - globalPermissions, - permissions.enableImport - ); + const { globalPermissions } = this.dataService.fetchInfo(); - this.changeDetectorRef.markForCheck(); - }); + this.hasPermissionToImportOrders = hasPermission( + globalPermissions, + permissions.enableImport + ); this.deviceType = this.deviceService.getDeviceInfo().deviceType; diff --git a/apps/client/src/app/services/data.service.ts b/apps/client/src/app/services/data.service.ts index 6c5f70915..0f52bb35e 100644 --- a/apps/client/src/app/services/data.service.ts +++ b/apps/client/src/app/services/data.service.ts @@ -29,6 +29,7 @@ import { permissions } from '@ghostfolio/common/permissions'; import { Order as OrderModel } from '@prisma/client'; import { Account as AccountModel } from '@prisma/client'; import { parseISO } from 'date-fns'; +import { cloneDeep } from 'lodash'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; @@ -92,21 +93,16 @@ export class DataService { return this.http.get('/api/export'); } - public fetchInfo() { - return this.http.get('/api/info').pipe( - map((data) => { - if ( - this.settingsStorageService.getSetting('utm_source') === - 'trusted-web-activity' - ) { - data.globalPermissions = data.globalPermissions.filter( - (permission) => permission !== permissions.enableSubscription - ); - } + public fetchInfo(): InfoItem { + const info = cloneDeep((window as any).info); - return data; - }) - ); + if (window.localStorage.getItem('utm_source') === 'trusted-web-activity') { + info.globalPermissions = info.globalPermissions.filter( + (permission) => permission !== permissions.enableSubscription + ); + } + + return info; } public fetchSymbolItem(aSymbol: string) { diff --git a/apps/client/src/environments/environment.prod.ts b/apps/client/src/environments/environment.prod.ts index 01ed05423..66b0c6745 100644 --- a/apps/client/src/environments/environment.prod.ts +++ b/apps/client/src/environments/environment.prod.ts @@ -1,6 +1,6 @@ export const environment = { lastPublish: '{BUILD_TIMESTAMP}', production: true, - stripePublicKey: '{STRIPE_PUBLIC_KEY}', + stripePublicKey: '', version: `v${require('../../../../package.json').version}` }; diff --git a/apps/client/src/main.ts b/apps/client/src/main.ts index 8689f7576..4653539bb 100644 --- a/apps/client/src/main.ts +++ b/apps/client/src/main.ts @@ -1,16 +1,33 @@ import { enableProdMode } from '@angular/core'; import { LOCALE_ID } from '@angular/core'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { InfoItem } from '@ghostfolio/common/interfaces'; +import { permissions } from '@ghostfolio/common/permissions'; import { AppModule } from './app/app.module'; import { environment } from './environments/environment'; -if (environment.production) { - enableProdMode(); -} +(async () => { + const response = await fetch('/api/info'); + const info: InfoItem = await response.json(); -platformBrowserDynamic() - .bootstrapModule(AppModule, { - providers: [{ provide: LOCALE_ID, useValue: 'de-CH' }] - }) - .catch((err) => console.error(err)); + if (window.localStorage.getItem('utm_source') === 'trusted-web-activity') { + info.globalPermissions = info.globalPermissions.filter( + (permission) => permission !== permissions.enableSubscription + ); + } + + (window as any).info = info; + + environment.stripePublicKey = info.stripePublicKey; + + if (environment.production) { + enableProdMode(); + } + + platformBrowserDynamic() + .bootstrapModule(AppModule, { + providers: [{ provide: LOCALE_ID, useValue: 'de-CH' }] + }) + .catch((err) => console.error(err)); +})(); diff --git a/libs/common/src/lib/interfaces/info-item.interface.ts b/libs/common/src/lib/interfaces/info-item.interface.ts index e8a794840..57d970993 100644 --- a/libs/common/src/lib/interfaces/info-item.interface.ts +++ b/libs/common/src/lib/interfaces/info-item.interface.ts @@ -14,5 +14,6 @@ export interface InfoItem { }; platforms: { id: string; name: string }[]; statistics: Statistics; + stripePublicKey?: string; subscriptions: Subscription[]; } diff --git a/replace.build.js b/replace.build.js index 90cc3ff5c..0811fa969 100644 --- a/replace.build.js +++ b/replace.build.js @@ -16,7 +16,7 @@ const buildTimestamp = `${formatWithTwoDigits( )}:${formatWithTwoDigits(now.getMinutes())}`; try { - let changedFiles = replace.sync({ + const changedFiles = replace.sync({ files: './dist/apps/client/main.*.js', from: /{BUILD_TIMESTAMP}/g, to: buildTimestamp, @@ -24,14 +24,6 @@ try { }); console.log('Build version set: ' + buildTimestamp); console.log(changedFiles); - - changedFiles = replace.sync({ - files: './dist/apps/client/main.*.js', - from: /{STRIPE_PUBLIC_KEY}/g, - to: process.env.STRIPE_PUBLIC_KEY ?? '', - allowEmptyPaths: false - }); - console.log(changedFiles); } catch (error) { console.error('Error occurred:', error); }