Browse Source

Respect data source in symbol data endpoint

pull/370/head
Thomas 4 years ago
parent
commit
40eeb6c597
  1. 10
      apps/api/src/app/symbol/symbol.controller.ts
  2. 12
      apps/api/src/app/symbol/symbol.service.ts
  3. 4
      apps/api/src/services/data-provider/data-provider.service.ts
  4. 6
      apps/client/src/app/pages/home/home-page.component.ts
  5. 48
      apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.component.ts
  6. 7
      apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.html
  7. 17
      apps/client/src/app/services/data.service.ts

10
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<SymbolItem> {
const result = await this.symbolService.get(symbol);
public async getSymbolData(
@Param('dataSource') dataSource: DataSource,
@Param('symbol') symbol: string
): Promise<SymbolItem> {
const result = await this.symbolService.get({ dataSource, symbol });
if (!result || isEmpty(result)) {
throw new HttpException(

12
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<SymbolItem> {
const response = await this.dataProviderService.get([aSymbol]);
const { currency, dataSource, marketPrice } = response[aSymbol] ?? {};
public async get({
dataSource,
symbol
}: {
dataSource: DataSource;
symbol: string;
}): Promise<SymbolItem> {
const response = await this.dataProviderService.get([symbol]);
const { currency, marketPrice } = response[symbol] ?? {};
if (dataSource && marketPrice) {
return {

4
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()

6
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;

48
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<LookupItem[]>;
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;

7
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()"
/>
<mat-autocomplete
#auto="matAutocomplete"
#autocomplete="matAutocomplete"
[displayWith]="displayFn"
(optionSelected)="onUpdateSymbol($event)"
>
<ng-container>
<mat-option
*ngFor="let lookupItem of filteredLookupItems | async"
class="autocomplete"
[value]="lookupItem.symbol"
[value]="lookupItem"
>
<span class="mr-2 symbol">{{ lookupItem.symbol | gfSymbol }}</span
><span><b>{{ lookupItem.name }}</b></span>

17
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<SymbolItem>(`/api/symbol/${aSymbol}`);
public fetchSymbolItem({
dataSource,
symbol
}: {
dataSource: DataSource;
symbol: string;
}) {
return this.http.get<SymbolItem>(`/api/symbol/${dataSource}/${symbol}`);
}
public fetchPositions({

Loading…
Cancel
Save