diff --git a/CHANGELOG.md b/CHANGELOG.md index 907889461..827973391 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Added + +- Added the generic scraper symbols to the symbol lookup results + ## 0.93.0 - 26.04.2021 ### Changed diff --git a/apps/api/src/app/symbol/symbol.service.ts b/apps/api/src/app/symbol/symbol.service.ts index 1bb0435bb..611c91f38 100644 --- a/apps/api/src/app/symbol/symbol.service.ts +++ b/apps/api/src/app/symbol/symbol.service.ts @@ -1,4 +1,6 @@ +import { DataGatheringService } from '@ghostfolio/api/services/data-gathering.service'; import { DataProviderService } from '@ghostfolio/api/services/data-provider.service'; +import { GhostfolioScraperApiService } from '@ghostfolio/api/services/data-provider/ghostfolio-scraper-api/ghostfolio-scraper-api.service'; import { convertFromYahooSymbol } from '@ghostfolio/api/services/data-provider/yahoo-finance/yahoo-finance.service'; import { Injectable } from '@nestjs/common'; import { Currency } from '@prisma/client'; @@ -10,7 +12,8 @@ import { SymbolItem } from './interfaces/symbol-item.interface'; @Injectable() export class SymbolService { public constructor( - private readonly dataProviderService: DataProviderService + private readonly dataProviderService: DataProviderService, + private readonly ghostfolioScraperApiService: GhostfolioScraperApiService ) {} public async get(aSymbol: string): Promise { @@ -23,18 +26,36 @@ export class SymbolService { }; } - public async lookup(aQuery: string): Promise { + public async lookup(aQuery = ''): Promise { + const query = aQuery.toLowerCase(); + const results: LookupItem[] = []; + + if (!query) { + return results; + } + const get = bent( - `https://query1.finance.yahoo.com/v1/finance/search?q=${aQuery}&lang=en-US®ion=US"esCount=8&newsCount=0&enableFuzzyQuery=false"esQueryId=tss_match_phrase_query&multiQuoteQueryId=multi_quote_single_token_query&newsQueryId=news_cie_vespa&enableCb=true&enableNavLinks=false&enableEnhancedTrivialQuery=true`, + `https://query1.finance.yahoo.com/v1/finance/search?q=${query}&lang=en-US®ion=US"esCount=8&newsCount=0&enableFuzzyQuery=false"esQueryId=tss_match_phrase_query&multiQuoteQueryId=multi_quote_single_token_query&newsQueryId=news_cie_vespa&enableCb=true&enableNavLinks=false&enableEnhancedTrivialQuery=true`, 'GET', 'json', 200 ); + // Add custom symbols + const scraperConfigurations = await this.ghostfolioScraperApiService.getScraperConfigurations(); + scraperConfigurations.forEach((scraperConfiguration) => { + if (scraperConfiguration.name.toLowerCase().startsWith(query)) { + results.push({ + name: scraperConfiguration.name, + symbol: scraperConfiguration.symbol + }); + } + }); + try { const { quotes } = await get(); - return quotes + const searchResult = quotes .filter(({ isYahooFinance }) => { return isYahooFinance; }) @@ -59,6 +80,8 @@ export class SymbolService { symbol: convertFromYahooSymbol(symbol) }; }); + + return results.concat(searchResult); } catch (error) { console.error(error); diff --git a/apps/api/src/services/data-gathering.service.ts b/apps/api/src/services/data-gathering.service.ts index c84148307..bdbd2d7d0 100644 --- a/apps/api/src/services/data-gathering.service.ts +++ b/apps/api/src/services/data-gathering.service.ts @@ -18,6 +18,7 @@ import { import { ConfigurationService } from './configuration.service'; import { DataProviderService } from './data-provider.service'; +import { GhostfolioScraperApiService } from './data-provider/ghostfolio-scraper-api/ghostfolio-scraper-api.service'; import { PrismaService } from './prisma.service'; @Injectable() @@ -25,6 +26,7 @@ export class DataGatheringService { public constructor( private readonly configurationService: ConfigurationService, private readonly dataProviderService: DataProviderService, + private readonly ghostfolioScraperApi: GhostfolioScraperApiService, private prisma: PrismaService ) {} @@ -183,6 +185,17 @@ export class DataGatheringService { } } + public async getCustomSymbolsToGather(startDate?: Date) { + const scraperConfigurations = await this.ghostfolioScraperApi.getScraperConfigurations(); + + return scraperConfigurations.map((scraperConfiguration) => { + return { + date: startDate, + symbol: scraperConfiguration.symbol + }; + }); + } + private getBenchmarksToGather(startDate: Date) { const benchmarksToGather = benchmarks.map((symbol) => { return { @@ -201,32 +214,6 @@ export class DataGatheringService { return benchmarksToGather; } - private async getCustomSymbolsToGather(startDate: Date) { - const customSymbolsToGather = []; - - if (this.configurationService.get('ENABLE_FEATURE_CUSTOM_SYMBOLS')) { - try { - const { - value: scraperConfigString - } = await this.prisma.property.findFirst({ - select: { - value: true - }, - where: { key: 'SCRAPER_CONFIG' } - }); - - JSON.parse(scraperConfigString).forEach((item) => { - customSymbolsToGather.push({ - date: startDate, - symbol: item.symbol - }); - }); - } catch {} - } - - return customSymbolsToGather; - } - private async getSymbols7D(): Promise<{ date: Date; symbol: string }[]> { const startDate = subDays(resetHours(new Date()), 7); diff --git a/apps/api/src/services/data-provider/ghostfolio-scraper-api/ghostfolio-scraper-api.service.ts b/apps/api/src/services/data-provider/ghostfolio-scraper-api/ghostfolio-scraper-api.service.ts index 7b5fba378..f848ffe7a 100644 --- a/apps/api/src/services/data-provider/ghostfolio-scraper-api/ghostfolio-scraper-api.service.ts +++ b/apps/api/src/services/data-provider/ghostfolio-scraper-api/ghostfolio-scraper-api.service.ts @@ -12,6 +12,7 @@ import { MarketState } from '../../interfaces/interfaces'; import { PrismaService } from '../../prisma.service'; +import { ScraperConfig } from './interfaces/scraper-config.interface'; @Injectable() export class GhostfolioScraperApiService implements DataProviderInterface { @@ -29,7 +30,7 @@ export class GhostfolioScraperApiService implements DataProviderInterface { try { const symbol = aSymbols[0]; - const scraperConfig = await this.getScraperConfig(symbol); + const scraperConfig = await this.getScraperConfigurationBySymbol(symbol); const { marketPrice } = await this.prisma.marketData.findFirst({ orderBy: { @@ -70,15 +71,17 @@ export class GhostfolioScraperApiService implements DataProviderInterface { try { const symbol = aSymbols[0]; - const scraperConfig = await this.getScraperConfig(symbol); + const scraperConfiguration = await this.getScraperConfigurationBySymbol( + symbol + ); - const get = bent(scraperConfig?.url, 'GET', 'string', 200, {}); + const get = bent(scraperConfiguration?.url, 'GET', 'string', 200, {}); const html = await get(); const $ = cheerio.load(html); const value = this.extractNumberFromString( - $(scraperConfig?.selector).text() + $(scraperConfiguration?.selector).text() ); return { @@ -95,18 +98,7 @@ export class GhostfolioScraperApiService implements DataProviderInterface { return {}; } - private extractNumberFromString(aString: string): number { - try { - const [numberString] = aString.match( - GhostfolioScraperApiService.NUMERIC_REGEXP - ); - return parseFloat(numberString.trim()); - } catch { - return undefined; - } - } - - private async getScraperConfig(aSymbol: string) { + public async getScraperConfigurations(): Promise { try { const { value: scraperConfigString @@ -117,11 +109,27 @@ export class GhostfolioScraperApiService implements DataProviderInterface { where: { key: 'SCRAPER_CONFIG' } }); - return JSON.parse(scraperConfigString).find((item) => { - return item.symbol === aSymbol; - }); + return JSON.parse(scraperConfigString); } catch {} - return {}; + return []; + } + + private extractNumberFromString(aString: string): number { + try { + const [numberString] = aString.match( + GhostfolioScraperApiService.NUMERIC_REGEXP + ); + return parseFloat(numberString.trim()); + } catch { + return undefined; + } + } + + private async getScraperConfigurationBySymbol(aSymbol: string) { + const scraperConfigurations = await this.getScraperConfigurations(); + return scraperConfigurations.find((scraperConfiguration) => { + return scraperConfiguration.symbol === aSymbol; + }); } } diff --git a/apps/api/src/services/data-provider/ghostfolio-scraper-api/interfaces/scraper-config.interface.ts b/apps/api/src/services/data-provider/ghostfolio-scraper-api/interfaces/scraper-config.interface.ts new file mode 100644 index 000000000..989594169 --- /dev/null +++ b/apps/api/src/services/data-provider/ghostfolio-scraper-api/interfaces/scraper-config.interface.ts @@ -0,0 +1,9 @@ +import { Currency } from '.prisma/client'; + +export interface ScraperConfig { + currency: Currency; + name: string; + selector: string; + symbol: string; + url: string; +} diff --git a/apps/client/src/app/pages/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.html b/apps/client/src/app/pages/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.html index e66340be7..8ee43c7ec 100644 --- a/apps/client/src/app/pages/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.html +++ b/apps/client/src/app/pages/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.html @@ -25,7 +25,7 @@ class="autocomplete" [value]="lookupItem.symbol" > - {{ lookupItem.symbol }}{{ lookupItem.symbol | gfSymbol }}{{ lookupItem.name }} diff --git a/apps/client/src/app/pages/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.module.ts b/apps/client/src/app/pages/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.module.ts index f58e368d0..01cb51875 100644 --- a/apps/client/src/app/pages/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.module.ts +++ b/apps/client/src/app/pages/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.module.ts @@ -9,6 +9,7 @@ import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { MatSelectModule } from '@angular/material/select'; +import { GfSymbolModule } from '@ghostfolio/client/pipes/symbol/symbol.module'; import { CreateOrUpdateTransactionDialog } from './create-or-update-transaction-dialog.component'; @@ -17,6 +18,7 @@ import { CreateOrUpdateTransactionDialog } from './create-or-update-transaction- exports: [], imports: [ CommonModule, + GfSymbolModule, FormsModule, MatAutocompleteModule, MatButtonModule, diff --git a/apps/client/src/app/pages/transactions/create-or-update-transaction-dialog/interfaces/interfaces.ts b/apps/client/src/app/pages/transactions/create-or-update-transaction-dialog/interfaces/interfaces.ts index cd41949e6..66a62eecc 100644 --- a/apps/client/src/app/pages/transactions/create-or-update-transaction-dialog/interfaces/interfaces.ts +++ b/apps/client/src/app/pages/transactions/create-or-update-transaction-dialog/interfaces/interfaces.ts @@ -1,6 +1,7 @@ -import { Order } from '../../interfaces/order.interface'; import { Account } from '@prisma/client'; +import { Order } from '../../interfaces/order.interface'; + export interface CreateOrUpdateTransactionDialogParams { accountId: string; accounts: Account[];