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'; } from '@nestjs/common';
import { REQUEST } from '@nestjs/core'; import { REQUEST } from '@nestjs/core';
import { AuthGuard } from '@nestjs/passport'; import { AuthGuard } from '@nestjs/passport';
import { DataSource } from '@prisma/client';
import { StatusCodes, getReasonPhrase } from 'http-status-codes'; import { StatusCodes, getReasonPhrase } from 'http-status-codes';
import { isEmpty } from 'lodash'; import { isEmpty } from 'lodash';
@ -46,10 +47,13 @@ export class SymbolController {
/** /**
* Must be after /lookup * Must be after /lookup
*/ */
@Get(':symbol') @Get(':dataSource/:symbol')
@UseGuards(AuthGuard('jwt')) @UseGuards(AuthGuard('jwt'))
public async getPosition(@Param('symbol') symbol): Promise<SymbolItem> { public async getSymbolData(
const result = await this.symbolService.get(symbol); @Param('dataSource') dataSource: DataSource,
@Param('symbol') symbol: string
): Promise<SymbolItem> {
const result = await this.symbolService.get({ dataSource, symbol });
if (!result || isEmpty(result)) { if (!result || isEmpty(result)) {
throw new HttpException( throw new HttpException(

12
apps/api/src/app/symbol/symbol.service.ts

@ -13,9 +13,15 @@ export class SymbolService {
private readonly prismaService: PrismaService private readonly prismaService: PrismaService
) {} ) {}
public async get(aSymbol: string): Promise<SymbolItem> { public async get({
const response = await this.dataProviderService.get([aSymbol]); dataSource,
const { currency, dataSource, marketPrice } = response[aSymbol] ?? {}; symbol
}: {
dataSource: DataSource;
symbol: string;
}): Promise<SymbolItem> {
const response = await this.dataProviderService.get([symbol]);
const { currency, marketPrice } = response[symbol] ?? {};
if (dataSource && marketPrice) { if (dataSource && marketPrice) {
return { 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 { GhostfolioScraperApiService } from './ghostfolio-scraper-api/ghostfolio-scraper-api.service';
import { RakutenRapidApiService } from './rakuten-rapid-api/rakuten-rapid-api.service'; import { RakutenRapidApiService } from './rakuten-rapid-api/rakuten-rapid-api.service';
import { import {
convertToYahooFinanceSymbol, YahooFinanceService,
YahooFinanceService convertToYahooFinanceSymbol
} from './yahoo-finance/yahoo-finance.service'; } from './yahoo-finance/yahoo-finance.service';
@Injectable() @Injectable()

6
apps/client/src/app/pages/home/home-page.component.ts

@ -29,6 +29,7 @@ import {
} from '@ghostfolio/common/interfaces'; } from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { DateRange } from '@ghostfolio/common/types'; import { DateRange } from '@ghostfolio/common/types';
import { DataSource } from '@prisma/client';
import { DeviceDetectorService } from 'ngx-device-detector'; import { DeviceDetectorService } from 'ngx-device-detector';
import { Subject, Subscription } from 'rxjs'; import { Subject, Subscription } from 'rxjs';
import { takeUntil } from 'rxjs/operators'; import { takeUntil } from 'rxjs/operators';
@ -112,7 +113,10 @@ export class HomePageComponent implements OnDestroy, OnInit {
if (this.hasPermissionToAccessFearAndGreedIndex) { if (this.hasPermissionToAccessFearAndGreedIndex) {
this.dataService this.dataService
.fetchSymbolItem(ghostfolioFearAndGreedIndexSymbol) .fetchSymbolItem({
dataSource: DataSource.GHOSTFOLIO,
symbol: ghostfolioFearAndGreedIndexSymbol
})
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntil(this.unsubscribeSubject))
.subscribe(({ marketPrice }) => { .subscribe(({ marketPrice }) => {
this.fearAndGreedIndex = 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, ChangeDetectorRef,
Component, Component,
Inject, Inject,
OnDestroy OnDestroy,
ViewChild
} from '@angular/core'; } from '@angular/core';
import { FormControl, Validators } from '@angular/forms'; import { FormControl, Validators } from '@angular/forms';
import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete'; 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 { LookupItem } from '@ghostfolio/api/app/symbol/interfaces/lookup-item.interface';
import { DataService } from '@ghostfolio/client/services/data.service'; import { DataService } from '@ghostfolio/client/services/data.service';
import { Currency } from '@prisma/client'; import { Currency } from '@prisma/client';
import { isString } from 'lodash';
import { EMPTY, Observable, Subject } from 'rxjs'; import { EMPTY, Observable, Subject } from 'rxjs';
import { import {
catchError, catchError,
@ -31,13 +33,18 @@ import { CreateOrUpdateTransactionDialogParams } from './interfaces/interfaces';
templateUrl: 'create-or-update-transaction-dialog.html' templateUrl: 'create-or-update-transaction-dialog.html'
}) })
export class CreateOrUpdateTransactionDialog implements OnDestroy { export class CreateOrUpdateTransactionDialog implements OnDestroy {
@ViewChild('autocomplete') autocomplete;
public currencies: Currency[] = []; public currencies: Currency[] = [];
public currentMarketPrice = null; public currentMarketPrice = null;
public filteredLookupItems: Observable<LookupItem[]>; public filteredLookupItems: Observable<LookupItem[]>;
public isLoading = false; public isLoading = false;
public platforms: { id: string; name: string }[]; public platforms: { id: string; name: string }[];
public searchSymbolCtrl = new FormControl( public searchSymbolCtrl = new FormControl(
this.data.transaction.symbol, {
dataSource: this.data.transaction.dataSource,
name: this.data.transaction.symbol
},
Validators.required Validators.required
); );
@ -60,9 +67,9 @@ export class CreateOrUpdateTransactionDialog implements OnDestroy {
startWith(''), startWith(''),
debounceTime(400), debounceTime(400),
distinctUntilChanged(), distinctUntilChanged(),
switchMap((aQuery: string) => { switchMap((query: string) => {
if (aQuery) { if (isString(query)) {
return this.dataService.fetchSymbols(aQuery); return this.dataService.fetchSymbols(query);
} }
return []; return [];
@ -71,7 +78,10 @@ export class CreateOrUpdateTransactionDialog implements OnDestroy {
if (this.data.transaction.symbol) { if (this.data.transaction.symbol) {
this.dataService this.dataService
.fetchSymbolItem(this.data.transaction.symbol) .fetchSymbolItem({
dataSource: this.data.transaction.dataSource,
symbol: this.data.transaction.symbol
})
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntil(this.unsubscribeSubject))
.subscribe(({ marketPrice }) => { .subscribe(({ marketPrice }) => {
this.currentMarketPrice = marketPrice; this.currentMarketPrice = marketPrice;
@ -85,9 +95,21 @@ export class CreateOrUpdateTransactionDialog implements OnDestroy {
this.data.transaction.unitPrice = this.currentMarketPrice; this.data.transaction.unitPrice = this.currentMarketPrice;
} }
public displayFn(aLookupItem: LookupItem) {
return aLookupItem?.name ?? '';
}
public onBlurSymbol() { public onBlurSymbol() {
const symbol = this.searchSymbolCtrl.value; this.data.transaction.currency = null;
this.updateSymbol(symbol); 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 { public onCancel(): void {
@ -95,7 +117,8 @@ export class CreateOrUpdateTransactionDialog implements OnDestroy {
} }
public onUpdateSymbol(event: MatAutocompleteSelectedEvent) { 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() { public ngOnDestroy() {
@ -106,10 +129,15 @@ export class CreateOrUpdateTransactionDialog implements OnDestroy {
private updateSymbol(symbol: string) { private updateSymbol(symbol: string) {
this.isLoading = true; this.isLoading = true;
this.searchSymbolCtrl.setErrors(null);
this.data.transaction.symbol = symbol; this.data.transaction.symbol = symbol;
this.dataService this.dataService
.fetchSymbolItem(this.data.transaction.symbol) .fetchSymbolItem({
dataSource: this.data.transaction.dataSource,
symbol: this.data.transaction.symbol
})
.pipe( .pipe(
catchError(() => { catchError(() => {
this.data.transaction.currency = null; 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 matInput
required required
[formControl]="searchSymbolCtrl" [formControl]="searchSymbolCtrl"
[matAutocomplete]="auto" [matAutocomplete]="autocomplete"
(blur)="onBlurSymbol()" (blur)="onBlurSymbol()"
/> />
<mat-autocomplete <mat-autocomplete
#auto="matAutocomplete" #autocomplete="matAutocomplete"
[displayWith]="displayFn"
(optionSelected)="onUpdateSymbol($event)" (optionSelected)="onUpdateSymbol($event)"
> >
<ng-container> <ng-container>
<mat-option <mat-option
*ngFor="let lookupItem of filteredLookupItems | async" *ngFor="let lookupItem of filteredLookupItems | async"
class="autocomplete" class="autocomplete"
[value]="lookupItem.symbol" [value]="lookupItem"
> >
<span class="mr-2 symbol">{{ lookupItem.symbol | gfSymbol }}</span <span class="mr-2 symbol">{{ lookupItem.symbol | gfSymbol }}</span
><span><b>{{ lookupItem.name }}</b></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 { InvestmentItem } from '@ghostfolio/common/interfaces/investment-item.interface';
import { permissions } from '@ghostfolio/common/permissions'; import { permissions } from '@ghostfolio/common/permissions';
import { DateRange } from '@ghostfolio/common/types'; import { DateRange } from '@ghostfolio/common/types';
import { Order as OrderModel } from '@prisma/client'; import {
import { Account as AccountModel } from '@prisma/client'; Account as AccountModel,
DataSource,
Order as OrderModel
} from '@prisma/client';
import { parseISO } from 'date-fns'; import { parseISO } from 'date-fns';
import { cloneDeep } from 'lodash'; import { cloneDeep } from 'lodash';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
@ -108,8 +111,14 @@ export class DataService {
return info; return info;
} }
public fetchSymbolItem(aSymbol: string) { public fetchSymbolItem({
return this.http.get<SymbolItem>(`/api/symbol/${aSymbol}`); dataSource,
symbol
}: {
dataSource: DataSource;
symbol: string;
}) {
return this.http.get<SymbolItem>(`/api/symbol/${dataSource}/${symbol}`);
} }
public fetchPositions({ public fetchPositions({

Loading…
Cancel
Save