diff --git a/CHANGELOG.md b/CHANGELOG.md index 64a2e28c1..7e468015e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Improved the support for the `MANUAL` data source - Improved the _Activities_ tab icon - Upgraded `prisma` from version `4.4.0` to `4.6.1` - Upgraded `yahoo-finance2` from version `2.3.6` to `2.3.10` diff --git a/apps/api/src/app/symbol/symbol.service.ts b/apps/api/src/app/symbol/symbol.service.ts index e24aa71b2..0575cfda1 100644 --- a/apps/api/src/app/symbol/symbol.service.ts +++ b/apps/api/src/app/symbol/symbol.service.ts @@ -32,7 +32,7 @@ export class SymbolService { ]); const { currency, marketPrice } = quotes[dataGatheringItem.symbol] ?? {}; - if (dataGatheringItem.dataSource && marketPrice) { + if (dataGatheringItem.dataSource && marketPrice >= 0) { let historicalData: HistoricalDataItem[] = []; if (includeHistoricalData > 0) { diff --git a/apps/api/src/services/configuration.service.ts b/apps/api/src/services/configuration.service.ts index 30e9d7c6f..75441c8c3 100644 --- a/apps/api/src/services/configuration.service.ts +++ b/apps/api/src/services/configuration.service.ts @@ -19,7 +19,7 @@ export class ConfigurationService { CACHE_TTL: num({ default: 1 }), DATA_SOURCE_PRIMARY: str({ default: DataSource.YAHOO }), DATA_SOURCES: json({ - default: [DataSource.GHOSTFOLIO, DataSource.YAHOO] + default: [DataSource.GHOSTFOLIO, DataSource.MANUAL, DataSource.YAHOO] }), ENABLE_FEATURE_BLOG: bool({ default: false }), ENABLE_FEATURE_CUSTOM_SYMBOLS: bool({ default: false }), diff --git a/apps/api/src/services/data-provider/manual/manual.service.ts b/apps/api/src/services/data-provider/manual/manual.service.ts index edcdd2cde..a364276ef 100644 --- a/apps/api/src/services/data-provider/manual/manual.service.ts +++ b/apps/api/src/services/data-provider/manual/manual.service.ts @@ -4,13 +4,18 @@ import { IDataProviderHistoricalResponse, IDataProviderResponse } from '@ghostfolio/api/services/interfaces/interfaces'; +import { PrismaService } from '@ghostfolio/api/services/prisma.service'; +import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile.service'; import { Granularity } from '@ghostfolio/common/types'; -import { Injectable } from '@nestjs/common'; +import { Injectable, Logger } from '@nestjs/common'; import { DataSource, SymbolProfile } from '@prisma/client'; @Injectable() export class ManualService implements DataProviderInterface { - public constructor() {} + public constructor( + private readonly prismaService: PrismaService, + private readonly symbolProfileService: SymbolProfileService + ) {} public canHandle(symbol: string) { return false; @@ -42,10 +47,77 @@ export class ManualService implements DataProviderInterface { public async getQuotes( aSymbols: string[] ): Promise<{ [symbol: string]: IDataProviderResponse }> { + const response: { [symbol: string]: IDataProviderResponse } = {}; + + if (aSymbols.length <= 0) { + return response; + } + + try { + const symbolProfiles = + await this.symbolProfileService.getSymbolProfilesBySymbols(aSymbols); + + const marketData = await this.prismaService.marketData.findMany({ + distinct: ['symbol'], + orderBy: { + date: 'desc' + }, + take: aSymbols.length, + where: { + symbol: { + in: aSymbols + } + } + }); + + for (const symbolProfile of symbolProfiles) { + response[symbolProfile.symbol] = { + currency: symbolProfile.currency, + dataSource: this.getName(), + marketPrice: + marketData.find((marketDataItem) => { + return marketDataItem.symbol === symbolProfile.symbol; + })?.marketPrice ?? 0, + marketState: 'delayed' + }; + } + + return response; + } catch (error) { + Logger.error(error, 'ManualService'); + } + return {}; } public async search(aQuery: string): Promise<{ items: LookupItem[] }> { - return { items: [] }; + const items = await this.prismaService.symbolProfile.findMany({ + select: { + currency: true, + dataSource: true, + name: true, + symbol: true + }, + where: { + OR: [ + { + dataSource: this.getName(), + name: { + mode: 'insensitive', + startsWith: aQuery + } + }, + { + dataSource: this.getName(), + symbol: { + mode: 'insensitive', + startsWith: aQuery + } + } + ] + } + }); + + return { items }; } }