From 918f25f52c56b65dc6a39bcaadfdabc48a79e656 Mon Sep 17 00:00:00 2001 From: Thomas <4159106+dtslvr@users.noreply.github.com> Date: Tue, 14 Sep 2021 22:18:30 +0200 Subject: [PATCH] Respect data source in symbol data endpoint --- apps/api/src/app/symbol/symbol.controller.ts | 10 ++-- apps/api/src/app/symbol/symbol.service.ts | 12 +++-- .../data-provider/data-provider.service.ts | 4 +- .../src/app/pages/home/home-page.component.ts | 6 ++- ...-or-update-transaction-dialog.component.ts | 48 +++++++++++++++---- .../create-or-update-transaction-dialog.html | 7 +-- apps/client/src/app/services/data.service.ts | 17 +++++-- 7 files changed, 78 insertions(+), 26 deletions(-) diff --git a/apps/api/src/app/symbol/symbol.controller.ts b/apps/api/src/app/symbol/symbol.controller.ts index 4cb52c6a9..8054d8194 100644 --- a/apps/api/src/app/symbol/symbol.controller.ts +++ b/apps/api/src/app/symbol/symbol.controller.ts @@ -10,6 +10,7 @@ import { } from '@nestjs/common'; import { REQUEST } from '@nestjs/core'; import { AuthGuard } from '@nestjs/passport'; +import { DataSource } from '@prisma/client'; import { StatusCodes, getReasonPhrase } from 'http-status-codes'; import { isEmpty } from 'lodash'; @@ -46,10 +47,13 @@ export class SymbolController { /** * Must be after /lookup */ - @Get(':symbol') + @Get(':dataSource/:symbol') @UseGuards(AuthGuard('jwt')) - public async getPosition(@Param('symbol') symbol): Promise { - const result = await this.symbolService.get(symbol); + public async getSymbolData( + @Param('dataSource') dataSource: DataSource, + @Param('symbol') symbol: string + ): Promise { + const result = await this.symbolService.get({ dataSource, symbol }); if (!result || isEmpty(result)) { throw new HttpException( diff --git a/apps/api/src/app/symbol/symbol.service.ts b/apps/api/src/app/symbol/symbol.service.ts index 3fdb2f753..5d94230cc 100644 --- a/apps/api/src/app/symbol/symbol.service.ts +++ b/apps/api/src/app/symbol/symbol.service.ts @@ -13,9 +13,15 @@ export class SymbolService { private readonly prismaService: PrismaService ) {} - public async get(aSymbol: string): Promise { - const response = await this.dataProviderService.get([aSymbol]); - const { currency, dataSource, marketPrice } = response[aSymbol] ?? {}; + public async get({ + dataSource, + symbol + }: { + dataSource: DataSource; + symbol: string; + }): Promise { + const response = await this.dataProviderService.get([symbol]); + const { currency, marketPrice } = response[symbol] ?? {}; if (dataSource && marketPrice) { return { diff --git a/apps/api/src/services/data-provider/data-provider.service.ts b/apps/api/src/services/data-provider/data-provider.service.ts index 9e57129f3..415f8dc7f 100644 --- a/apps/api/src/services/data-provider/data-provider.service.ts +++ b/apps/api/src/services/data-provider/data-provider.service.ts @@ -20,8 +20,8 @@ import { AlphaVantageService } from './alpha-vantage/alpha-vantage.service'; import { GhostfolioScraperApiService } from './ghostfolio-scraper-api/ghostfolio-scraper-api.service'; import { RakutenRapidApiService } from './rakuten-rapid-api/rakuten-rapid-api.service'; import { - convertToYahooFinanceSymbol, - YahooFinanceService + YahooFinanceService, + convertToYahooFinanceSymbol } from './yahoo-finance/yahoo-finance.service'; @Injectable() diff --git a/apps/client/src/app/pages/home/home-page.component.ts b/apps/client/src/app/pages/home/home-page.component.ts index ce9b96956..91e02b7e7 100644 --- a/apps/client/src/app/pages/home/home-page.component.ts +++ b/apps/client/src/app/pages/home/home-page.component.ts @@ -29,6 +29,7 @@ import { } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { DateRange } from '@ghostfolio/common/types'; +import { DataSource } from '@prisma/client'; import { DeviceDetectorService } from 'ngx-device-detector'; import { Subject, Subscription } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; @@ -112,7 +113,10 @@ export class HomePageComponent implements OnDestroy, OnInit { if (this.hasPermissionToAccessFearAndGreedIndex) { this.dataService - .fetchSymbolItem(ghostfolioFearAndGreedIndexSymbol) + .fetchSymbolItem({ + dataSource: DataSource.GHOSTFOLIO, + symbol: ghostfolioFearAndGreedIndexSymbol + }) .pipe(takeUntil(this.unsubscribeSubject)) .subscribe(({ marketPrice }) => { this.fearAndGreedIndex = marketPrice; diff --git a/apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.component.ts b/apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.component.ts index a8473ee64..52c92449b 100644 --- a/apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.component.ts +++ b/apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.component.ts @@ -3,7 +3,8 @@ import { ChangeDetectorRef, Component, Inject, - OnDestroy + OnDestroy, + ViewChild } from '@angular/core'; import { FormControl, Validators } from '@angular/forms'; import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete'; @@ -11,6 +12,7 @@ import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { LookupItem } from '@ghostfolio/api/app/symbol/interfaces/lookup-item.interface'; import { DataService } from '@ghostfolio/client/services/data.service'; import { Currency } from '@prisma/client'; +import { isString } from 'lodash'; import { EMPTY, Observable, Subject } from 'rxjs'; import { catchError, @@ -31,13 +33,18 @@ import { CreateOrUpdateTransactionDialogParams } from './interfaces/interfaces'; templateUrl: 'create-or-update-transaction-dialog.html' }) export class CreateOrUpdateTransactionDialog implements OnDestroy { + @ViewChild('autocomplete') autocomplete; + public currencies: Currency[] = []; public currentMarketPrice = null; public filteredLookupItems: Observable; public isLoading = false; public platforms: { id: string; name: string }[]; public searchSymbolCtrl = new FormControl( - this.data.transaction.symbol, + { + dataSource: this.data.transaction.dataSource, + name: this.data.transaction.symbol + }, Validators.required ); @@ -60,9 +67,9 @@ export class CreateOrUpdateTransactionDialog implements OnDestroy { startWith(''), debounceTime(400), distinctUntilChanged(), - switchMap((aQuery: string) => { - if (aQuery) { - return this.dataService.fetchSymbols(aQuery); + switchMap((query: string) => { + if (isString(query)) { + return this.dataService.fetchSymbols(query); } return []; @@ -71,7 +78,10 @@ export class CreateOrUpdateTransactionDialog implements OnDestroy { if (this.data.transaction.symbol) { this.dataService - .fetchSymbolItem(this.data.transaction.symbol) + .fetchSymbolItem({ + dataSource: this.data.transaction.dataSource, + symbol: this.data.transaction.symbol + }) .pipe(takeUntil(this.unsubscribeSubject)) .subscribe(({ marketPrice }) => { this.currentMarketPrice = marketPrice; @@ -85,9 +95,21 @@ export class CreateOrUpdateTransactionDialog implements OnDestroy { this.data.transaction.unitPrice = this.currentMarketPrice; } + public displayFn(aLookupItem: LookupItem) { + return aLookupItem?.name ?? ''; + } + public onBlurSymbol() { - const symbol = this.searchSymbolCtrl.value; - this.updateSymbol(symbol); + this.data.transaction.currency = null; + this.data.transaction.dataSource = null; + + if (this.autocomplete.isOpen) { + this.searchSymbolCtrl.setErrors({ incorrect: true }); + } else { + this.data.transaction.unitPrice = null; + } + + this.changeDetectorRef.markForCheck(); } public onCancel(): void { @@ -95,7 +117,8 @@ export class CreateOrUpdateTransactionDialog implements OnDestroy { } public onUpdateSymbol(event: MatAutocompleteSelectedEvent) { - this.updateSymbol(event.option.value); + this.data.transaction.dataSource = event.option.value.dataSource; + this.updateSymbol(event.option.value.symbol); } public ngOnDestroy() { @@ -106,10 +129,15 @@ export class CreateOrUpdateTransactionDialog implements OnDestroy { private updateSymbol(symbol: string) { this.isLoading = true; + this.searchSymbolCtrl.setErrors(null); + this.data.transaction.symbol = symbol; this.dataService - .fetchSymbolItem(this.data.transaction.symbol) + .fetchSymbolItem({ + dataSource: this.data.transaction.dataSource, + symbol: this.data.transaction.symbol + }) .pipe( catchError(() => { this.data.transaction.currency = null; diff --git a/apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.html b/apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.html index f7a18abca..8af61bc35 100644 --- a/apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.html +++ b/apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.html @@ -28,18 +28,19 @@ matInput required [formControl]="searchSymbolCtrl" - [matAutocomplete]="auto" + [matAutocomplete]="autocomplete" (blur)="onBlurSymbol()" /> {{ lookupItem.symbol | gfSymbol }}{{ lookupItem.name }} diff --git a/apps/client/src/app/services/data.service.ts b/apps/client/src/app/services/data.service.ts index 1d3b56361..936efc888 100644 --- a/apps/client/src/app/services/data.service.ts +++ b/apps/client/src/app/services/data.service.ts @@ -29,8 +29,11 @@ import { import { InvestmentItem } from '@ghostfolio/common/interfaces/investment-item.interface'; import { permissions } from '@ghostfolio/common/permissions'; import { DateRange } from '@ghostfolio/common/types'; -import { Order as OrderModel } from '@prisma/client'; -import { Account as AccountModel } from '@prisma/client'; +import { + Account as AccountModel, + DataSource, + Order as OrderModel +} from '@prisma/client'; import { parseISO } from 'date-fns'; import { cloneDeep } from 'lodash'; import { Observable } from 'rxjs'; @@ -108,8 +111,14 @@ export class DataService { return info; } - public fetchSymbolItem(aSymbol: string) { - return this.http.get(`/api/symbol/${aSymbol}`); + public fetchSymbolItem({ + dataSource, + symbol + }: { + dataSource: DataSource; + symbol: string; + }) { + return this.http.get(`/api/symbol/${dataSource}/${symbol}`); } public fetchPositions({