Feature/set public stripe key dynamically (#216)

* Set public Stripe key dynamically

* Update changelog
pull/217/head
Thomas 3 years ago committed by GitHub
parent 39a76f7f40
commit 51fbc538ca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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 current pricing plan
- Improved the styling of the transaction type badge - 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` - Upgraded `angular-material-css-vars` from version `2.0.0` to `2.1.0`
### Fixed ### Fixed

@ -20,6 +20,7 @@ export class InfoService {
) {} ) {}
public async get(): Promise<InfoItem> { public async get(): Promise<InfoItem> {
const info: Partial<InfoItem> = {};
const platforms = await this.prisma.platform.findMany({ const platforms = await this.prisma.platform.findMany({
orderBy: { name: 'asc' }, orderBy: { name: 'asc' },
select: { id: true, name: true } select: { id: true, name: true }
@ -41,9 +42,12 @@ export class InfoService {
if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) { if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) {
globalPermissions.push(permissions.enableSubscription); globalPermissions.push(permissions.enableSubscription);
info.stripePublicKey = this.configurationService.get('STRIPE_PUBLIC_KEY');
} }
return { return {
...info,
globalPermissions, globalPermissions,
platforms, platforms,
currencies: Object.values(Currency), currencies: Object.values(Currency),

@ -30,6 +30,7 @@ export class ConfigurationService {
REDIS_HOST: str({ default: 'localhost' }), REDIS_HOST: str({ default: 'localhost' }),
REDIS_PORT: port({ default: 6379 }), REDIS_PORT: port({ default: 6379 }),
ROOT_URL: str({ default: 'http://localhost:4200' }), ROOT_URL: str({ default: 'http://localhost:4200' }),
STRIPE_PUBLIC_KEY: str({ default: '' }),
STRIPE_SECRET_KEY: str({ default: '' }), STRIPE_SECRET_KEY: str({ default: '' }),
WEB_AUTH_RP_ID: host({ default: 'localhost' }) WEB_AUTH_RP_ID: host({ default: 'localhost' })
}); });

@ -20,6 +20,7 @@ export interface Environment extends CleanedEnvAccessors {
REDIS_HOST: string; REDIS_HOST: string;
REDIS_PORT: number; REDIS_PORT: number;
ROOT_URL: string; ROOT_URL: string;
STRIPE_PUBLIC_KEY: string;
STRIPE_SECRET_KEY: string; STRIPE_SECRET_KEY: string;
WEB_AUTH_RP_ID: string; WEB_AUTH_RP_ID: string;
} }

@ -56,13 +56,6 @@ export class AppComponent implements OnDestroy, OnInit {
public ngOnInit() { public ngOnInit() {
this.deviceType = this.deviceService.getDeviceInfo().deviceType; this.deviceType = this.deviceService.getDeviceInfo().deviceType;
this.dataService
.fetchInfo()
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe((info) => {
this.info = info;
});
this.router.events this.router.events
.pipe(filter((event) => event instanceof NavigationEnd)) .pipe(filter((event) => event instanceof NavigationEnd))
.subscribe(() => { .subscribe(() => {
@ -70,6 +63,8 @@ export class AppComponent implements OnDestroy, OnInit {
const urlSegmentGroup = urlTree.root.children[PRIMARY_OUTLET]; const urlSegmentGroup = urlTree.root.children[PRIMARY_OUTLET];
const urlSegments = urlSegmentGroup.segments; const urlSegments = urlSegmentGroup.segments;
this.currentRoute = urlSegments[0].path; this.currentRoute = urlSegments[0].path;
this.info = this.dataService.fetchInfo();
}); });
this.userService.stateChanged this.userService.stateChanged

@ -15,7 +15,7 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { MaterialCssVarsModule } from 'angular-material-css-vars'; import { MaterialCssVarsModule } from 'angular-material-css-vars';
import { MarkdownModule } from 'ngx-markdown'; import { MarkdownModule } from 'ngx-markdown';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; 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 { environment } from '../environments/environment';
import { CustomDateAdapter } from './adapter/custom-date-adapter'; 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 { httpResponseInterceptorProviders } from './core/http-response.interceptor';
import { LanguageService } from './core/language.service'; import { LanguageService } from './core/language.service';
export function NgxStripeFactory(): string {
return environment.stripePublicKey;
}
@NgModule({ @NgModule({
declarations: [AppComponent], declarations: [AppComponent],
imports: [ imports: [
@ -57,7 +61,11 @@ import { LanguageService } from './core/language.service';
useClass: CustomDateAdapter, useClass: CustomDateAdapter,
deps: [LanguageService, MAT_DATE_LOCALE, Platform] 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] bootstrap: [AppComponent]
}) })

@ -39,10 +39,7 @@ export class AboutPageComponent implements OnDestroy, OnInit {
* Initializes the controller * Initializes the controller
*/ */
public ngOnInit() { public ngOnInit() {
this.dataService const { globalPermissions, statistics } = this.dataService.fetchInfo();
.fetchInfo()
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe(({ globalPermissions, statistics }) => {
this.hasPermissionForStatistics = hasPermission( this.hasPermissionForStatistics = hasPermission(
globalPermissions, globalPermissions,
permissions.enableStatistics permissions.enableStatistics
@ -50,9 +47,6 @@ export class AboutPageComponent implements OnDestroy, OnInit {
this.statistics = statistics; this.statistics = statistics;
this.changeDetectorRef.markForCheck();
});
this.userService.stateChanged this.userService.stateChanged
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntil(this.unsubscribeSubject))
.subscribe((state) => { .subscribe((state) => {

@ -54,10 +54,8 @@ export class AccountPageComponent implements OnDestroy, OnInit {
private userService: UserService, private userService: UserService,
public webAuthnService: WebAuthnService public webAuthnService: WebAuthnService
) { ) {
this.dataService const { currencies, globalPermissions, subscriptions } =
.fetchInfo() this.dataService.fetchInfo();
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe(({ currencies, globalPermissions, subscriptions }) => {
this.coupon = subscriptions?.[0]?.coupon; this.coupon = subscriptions?.[0]?.coupon;
this.couponId = subscriptions?.[0]?.couponId; this.couponId = subscriptions?.[0]?.couponId;
this.currencies = currencies; this.currencies = currencies;
@ -70,9 +68,6 @@ export class AccountPageComponent implements OnDestroy, OnInit {
this.price = subscriptions?.[0]?.price; this.price = subscriptions?.[0]?.price;
this.priceId = subscriptions?.[0]?.priceId; this.priceId = subscriptions?.[0]?.priceId;
this.changeDetectorRef.markForCheck();
});
this.userService.stateChanged this.userService.stateChanged
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntil(this.unsubscribeSubject))
.subscribe((state) => { .subscribe((state) => {

@ -8,7 +8,6 @@ import {
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { Currency } from '@prisma/client'; import { Currency } from '@prisma/client';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { DataService } from '../../../services/data.service'; import { DataService } from '../../../services/data.service';
import { CreateOrUpdateAccountDialogParams } from './interfaces/interfaces'; import { CreateOrUpdateAccountDialogParams } from './interfaces/interfaces';
@ -34,15 +33,10 @@ export class CreateOrUpdateAccountDialog implements OnDestroy {
) {} ) {}
ngOnInit() { ngOnInit() {
this.dataService const { currencies, platforms } = this.dataService.fetchInfo();
.fetchInfo()
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe(({ currencies, platforms }) => {
this.currencies = currencies; this.currencies = currencies;
this.platforms = platforms; this.platforms = platforms;
this.changeDetectorRef.markForCheck();
});
} }
public onCancel(): void { public onCancel(): void {

@ -3,10 +3,8 @@ import { Router } from '@angular/router';
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 { TokenStorageService } from '@ghostfolio/client/services/token-storage.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 { format } from 'date-fns';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
@Component({ @Component({
selector: 'gf-landing-page', selector: 'gf-landing-page',
@ -34,16 +32,11 @@ export class LandingPageComponent implements OnDestroy, OnInit {
* Initializes the controller * Initializes the controller
*/ */
public ngOnInit() { public ngOnInit() {
this.dataService const { demoAuthToken } = this.dataService.fetchInfo();
.fetchInfo()
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe(({ demoAuthToken }) => {
this.demoAuthToken = demoAuthToken; this.demoAuthToken = demoAuthToken;
this.initializeLineChart(); this.initializeLineChart();
this.changeDetectorRef.markForCheck();
});
} }
public initializeLineChart() { public initializeLineChart() {

@ -28,15 +28,10 @@ export class PricingPageComponent implements OnDestroy, OnInit {
private dataService: DataService, private dataService: DataService,
private userService: UserService private userService: UserService
) { ) {
this.dataService const { subscriptions } = this.dataService.fetchInfo();
.fetchInfo()
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe(({ subscriptions }) => {
this.coupon = this.price = subscriptions?.[0]?.coupon; this.coupon = this.price = subscriptions?.[0]?.coupon;
this.price = subscriptions?.[0]?.price; this.price = subscriptions?.[0]?.price;
this.changeDetectorRef.markForCheck();
});
} }
/** /**

@ -41,18 +41,13 @@ export class RegisterPageComponent implements OnDestroy, OnInit {
* Initializes the controller * Initializes the controller
*/ */
public ngOnInit() { public ngOnInit() {
this.dataService const { demoAuthToken, globalPermissions } = this.dataService.fetchInfo();
.fetchInfo()
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe(({ demoAuthToken, globalPermissions }) => {
this.demoAuthToken = demoAuthToken; this.demoAuthToken = demoAuthToken;
this.hasPermissionForSocialLogin = hasPermission( this.hasPermissionForSocialLogin = hasPermission(
globalPermissions, globalPermissions,
permissions.enableSocialLogin permissions.enableSocialLogin
); );
this.changeDetectorRef.markForCheck();
});
} }
public async createAccount() { public async createAccount() {

@ -50,16 +50,11 @@ export class CreateOrUpdateTransactionDialog implements OnDestroy {
) {} ) {}
ngOnInit() { ngOnInit() {
this.dataService const { currencies, platforms } = this.dataService.fetchInfo();
.fetchInfo()
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe(({ currencies, platforms }) => {
this.currencies = currencies; this.currencies = currencies;
this.platforms = platforms; this.platforms = platforms;
this.changeDetectorRef.markForCheck();
});
this.filteredLookupItems = this.searchSymbolCtrl.valueChanges.pipe( this.filteredLookupItems = this.searchSymbolCtrl.valueChanges.pipe(
startWith(''), startWith(''),
debounceTime(400), debounceTime(400),

@ -10,7 +10,6 @@ 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';
import { environment } from 'apps/client/src/environments/environment';
import { format, parseISO } from 'date-fns'; import { format, parseISO } from 'date-fns';
import { DeviceDetectorService } from 'ngx-device-detector'; import { DeviceDetectorService } from 'ngx-device-detector';
import { EMPTY, Subject, Subscription } from 'rxjs'; import { EMPTY, Subject, Subscription } from 'rxjs';
@ -72,18 +71,13 @@ export class TransactionsPageComponent implements OnDestroy, OnInit {
* Initializes the controller * Initializes the controller
*/ */
public ngOnInit() { public ngOnInit() {
this.dataService const { globalPermissions } = this.dataService.fetchInfo();
.fetchInfo()
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe(({ globalPermissions }) => {
this.hasPermissionToImportOrders = hasPermission( this.hasPermissionToImportOrders = hasPermission(
globalPermissions, globalPermissions,
permissions.enableImport permissions.enableImport
); );
this.changeDetectorRef.markForCheck();
});
this.deviceType = this.deviceService.getDeviceInfo().deviceType; this.deviceType = this.deviceService.getDeviceInfo().deviceType;
this.impersonationStorageService this.impersonationStorageService

@ -29,6 +29,7 @@ import { permissions } from '@ghostfolio/common/permissions';
import { Order as OrderModel } from '@prisma/client'; import { Order as OrderModel } from '@prisma/client';
import { Account as AccountModel } from '@prisma/client'; import { Account as AccountModel } from '@prisma/client';
import { parseISO } from 'date-fns'; import { parseISO } from 'date-fns';
import { cloneDeep } from 'lodash';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
@ -92,21 +93,16 @@ export class DataService {
return this.http.get<Export>('/api/export'); return this.http.get<Export>('/api/export');
} }
public fetchInfo() { public fetchInfo(): InfoItem {
return this.http.get<InfoItem>('/api/info').pipe( const info = cloneDeep((window as any).info);
map((data) => {
if ( if (window.localStorage.getItem('utm_source') === 'trusted-web-activity') {
this.settingsStorageService.getSetting('utm_source') === info.globalPermissions = info.globalPermissions.filter(
'trusted-web-activity'
) {
data.globalPermissions = data.globalPermissions.filter(
(permission) => permission !== permissions.enableSubscription (permission) => permission !== permissions.enableSubscription
); );
} }
return data; return info;
})
);
} }
public fetchSymbolItem(aSymbol: string) { public fetchSymbolItem(aSymbol: string) {

@ -1,6 +1,6 @@
export const environment = { export const environment = {
lastPublish: '{BUILD_TIMESTAMP}', lastPublish: '{BUILD_TIMESTAMP}',
production: true, production: true,
stripePublicKey: '{STRIPE_PUBLIC_KEY}', stripePublicKey: '',
version: `v${require('../../../../package.json').version}` version: `v${require('../../../../package.json').version}`
}; };

@ -1,10 +1,26 @@
import { enableProdMode } from '@angular/core'; import { enableProdMode } from '@angular/core';
import { LOCALE_ID } from '@angular/core'; import { LOCALE_ID } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 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 { AppModule } from './app/app.module';
import { environment } from './environments/environment'; import { environment } from './environments/environment';
(async () => {
const response = await fetch('/api/info');
const info: InfoItem = await response.json();
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) { if (environment.production) {
enableProdMode(); enableProdMode();
} }
@ -14,3 +30,4 @@ platformBrowserDynamic()
providers: [{ provide: LOCALE_ID, useValue: 'de-CH' }] providers: [{ provide: LOCALE_ID, useValue: 'de-CH' }]
}) })
.catch((err) => console.error(err)); .catch((err) => console.error(err));
})();

@ -14,5 +14,6 @@ export interface InfoItem {
}; };
platforms: { id: string; name: string }[]; platforms: { id: string; name: string }[];
statistics: Statistics; statistics: Statistics;
stripePublicKey?: string;
subscriptions: Subscription[]; subscriptions: Subscription[];
} }

@ -16,7 +16,7 @@ const buildTimestamp = `${formatWithTwoDigits(
)}:${formatWithTwoDigits(now.getMinutes())}`; )}:${formatWithTwoDigits(now.getMinutes())}`;
try { try {
let changedFiles = replace.sync({ const changedFiles = replace.sync({
files: './dist/apps/client/main.*.js', files: './dist/apps/client/main.*.js',
from: /{BUILD_TIMESTAMP}/g, from: /{BUILD_TIMESTAMP}/g,
to: buildTimestamp, to: buildTimestamp,
@ -24,14 +24,6 @@ try {
}); });
console.log('Build version set: ' + buildTimestamp); console.log('Build version set: ' + buildTimestamp);
console.log(changedFiles); 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) { } catch (error) {
console.error('Error occurred:', error); console.error('Error occurred:', error);
} }

Loading…
Cancel
Save