From 41f5801b5e27923e9ca3eb4e9ed001eda274507c Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sun, 4 Aug 2024 08:27:05 +0200 Subject: [PATCH] Feature/refactor unique asset type to asset profile identifier (#3636) * Refactoring --- apps/api/src/app/admin/admin.service.ts | 59 ++++++++++--------- .../src/app/benchmark/benchmark.controller.ts | 8 ++- .../src/app/benchmark/benchmark.service.ts | 15 +++-- apps/api/src/app/import/import.service.ts | 4 +- apps/api/src/app/logo/logo.service.ts | 4 +- apps/api/src/app/order/order.service.ts | 15 ++--- .../calculator/mwr/portfolio-calculator.ts | 7 ++- .../calculator/portfolio-calculator.ts | 22 +++---- .../calculator/twr/portfolio-calculator.ts | 7 ++- .../portfolio/current-rate.service.spec.ts | 16 ++--- .../src/app/portfolio/current-rate.service.ts | 15 +++-- .../interfaces/get-value-object.interface.ts | 4 +- .../app/redis-cache/redis-cache.service.ts | 4 +- .../interfaces/symbol-item.interface.ts | 7 ++- apps/api/src/app/symbol/symbol.service.ts | 6 +- .../data-gathering.processor.ts | 4 +- .../data-gathering/data-gathering.service.ts | 43 +++++++++----- .../data-provider/data-provider.service.ts | 14 ++--- .../exchange-rate-data.service.ts | 24 ++++---- .../api/src/services/interfaces/interfaces.ts | 7 ++- .../market-data/market-data.service.ts | 16 ++--- .../symbol-profile/symbol-profile.service.ts | 12 ++-- .../admin-market-data.component.ts | 16 +++-- .../admin-market-data.service.ts | 18 +++--- .../asset-profile-dialog.component.ts | 15 +++-- .../home-holdings/home-holdings.component.ts | 4 +- .../home-overview/home-overview.component.ts | 4 +- .../import-activities-dialog.component.ts | 19 +++--- .../import-activities-dialog.html | 8 +-- .../allocations/allocations-page.component.ts | 6 +- apps/client/src/app/services/admin.service.ts | 19 +++--- apps/client/src/app/services/data.service.ts | 14 ++--- libs/common/src/lib/helper.ts | 9 ++- .../lib/interfaces/admin-data.interface.ts | 6 +- ... => asset-profile-identifier.interface.ts} | 2 +- libs/common/src/lib/interfaces/index.ts | 4 +- .../interfaces/responses/errors.interface.ts | 4 +- .../src/lib/models/portfolio-snapshot.ts | 4 +- .../activities-table.component.ts | 6 +- .../lib/assistant/interfaces/interfaces.ts | 4 +- .../src/lib/benchmark/benchmark.component.ts | 13 +++- .../holdings-table.component.ts | 7 ++- .../portfolio-proportion-chart.component.ts | 7 ++- .../treemap-chart/treemap-chart.component.ts | 7 ++- 44 files changed, 289 insertions(+), 220 deletions(-) rename libs/common/src/lib/interfaces/{unique-asset.interface.ts => asset-profile-identifier.interface.ts} (68%) diff --git a/apps/api/src/app/admin/admin.service.ts b/apps/api/src/app/admin/admin.service.ts index b15c3efc3..50b781f54 100644 --- a/apps/api/src/app/admin/admin.service.ts +++ b/apps/api/src/app/admin/admin.service.ts @@ -21,9 +21,9 @@ import { AdminMarketData, AdminMarketDataDetails, AdminMarketDataItem, + AssetProfileIdentifier, EnhancedSymbolProfile, - Filter, - UniqueAsset + Filter } from '@ghostfolio/common/interfaces'; import { MarketDataPreset } from '@ghostfolio/common/types'; @@ -59,7 +59,9 @@ export class AdminService { currency, dataSource, symbol - }: UniqueAsset & { currency?: string }): Promise { + }: AssetProfileIdentifier & { currency?: string }): Promise< + SymbolProfile | never + > { try { if (dataSource === 'MANUAL') { return this.symbolProfileService.add({ @@ -96,7 +98,10 @@ export class AdminService { } } - public async deleteProfileData({ dataSource, symbol }: UniqueAsset) { + public async deleteProfileData({ + dataSource, + symbol + }: AssetProfileIdentifier) { await this.marketDataService.deleteMany({ dataSource, symbol }); await this.symbolProfileService.delete({ dataSource, symbol }); } @@ -325,7 +330,7 @@ export class AdminService { public async getMarketDataBySymbol({ dataSource, symbol - }: UniqueAsset): Promise { + }: AssetProfileIdentifier): Promise { let activitiesCount: EnhancedSymbolProfile['activitiesCount'] = 0; let currency: EnhancedSymbolProfile['currency'] = '-'; let dateOfFirstActivity: EnhancedSymbolProfile['dateOfFirstActivity']; @@ -386,7 +391,7 @@ export class AdminService { symbol, symbolMapping, url - }: Prisma.SymbolProfileUpdateInput & UniqueAsset) { + }: AssetProfileIdentifier & Prisma.SymbolProfileUpdateInput) { const symbolProfileOverrides = { assetClass: assetClass as AssetClass, assetSubClass: assetSubClass as AssetSubClass, @@ -394,28 +399,28 @@ export class AdminService { url: url as string }; - const updatedSymbolProfile: Prisma.SymbolProfileUpdateInput & UniqueAsset = - { - comment, - countries, - currency, - dataSource, - holdings, - scraperConfiguration, - sectors, - symbol, - symbolMapping, - ...(dataSource === 'MANUAL' - ? { assetClass, assetSubClass, name, url } - : { - SymbolProfileOverrides: { - upsert: { - create: symbolProfileOverrides, - update: symbolProfileOverrides - } + const updatedSymbolProfile: AssetProfileIdentifier & + Prisma.SymbolProfileUpdateInput = { + comment, + countries, + currency, + dataSource, + holdings, + scraperConfiguration, + sectors, + symbol, + symbolMapping, + ...(dataSource === 'MANUAL' + ? { assetClass, assetSubClass, name, url } + : { + SymbolProfileOverrides: { + upsert: { + create: symbolProfileOverrides, + update: symbolProfileOverrides } - }) - }; + } + }) + }; await this.symbolProfileService.updateSymbolProfile(updatedSymbolProfile); diff --git a/apps/api/src/app/benchmark/benchmark.controller.ts b/apps/api/src/app/benchmark/benchmark.controller.ts index 9c6331498..ea9ba8025 100644 --- a/apps/api/src/app/benchmark/benchmark.controller.ts +++ b/apps/api/src/app/benchmark/benchmark.controller.ts @@ -4,9 +4,9 @@ import { getInterval } from '@ghostfolio/api/helper/portfolio.helper'; import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.interceptor'; import { TransformDataSourceInResponseInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-response/transform-data-source-in-response.interceptor'; import type { + AssetProfileIdentifier, BenchmarkMarketDataDetails, - BenchmarkResponse, - UniqueAsset + BenchmarkResponse } from '@ghostfolio/common/interfaces'; import { permissions } from '@ghostfolio/common/permissions'; import type { DateRange, RequestWithUser } from '@ghostfolio/common/types'; @@ -41,7 +41,9 @@ export class BenchmarkController { @HasPermission(permissions.accessAdminControl) @Post() @UseGuards(AuthGuard('jwt'), HasPermissionGuard) - public async addBenchmark(@Body() { dataSource, symbol }: UniqueAsset) { + public async addBenchmark( + @Body() { dataSource, symbol }: AssetProfileIdentifier + ) { try { const benchmark = await this.benchmarkService.addBenchmark({ dataSource, diff --git a/apps/api/src/app/benchmark/benchmark.service.ts b/apps/api/src/app/benchmark/benchmark.service.ts index 27d91fd7d..e9495b44b 100644 --- a/apps/api/src/app/benchmark/benchmark.service.ts +++ b/apps/api/src/app/benchmark/benchmark.service.ts @@ -17,11 +17,11 @@ import { resetHours } from '@ghostfolio/common/helper'; import { + AssetProfileIdentifier, Benchmark, BenchmarkMarketDataDetails, BenchmarkProperty, - BenchmarkResponse, - UniqueAsset + BenchmarkResponse } from '@ghostfolio/common/interfaces'; import { BenchmarkTrend } from '@ghostfolio/common/types'; @@ -61,7 +61,10 @@ export class BenchmarkService { return 0; } - public async getBenchmarkTrends({ dataSource, symbol }: UniqueAsset) { + public async getBenchmarkTrends({ + dataSource, + symbol + }: AssetProfileIdentifier) { const historicalData = await this.marketDataService.marketDataItems({ orderBy: { date: 'desc' @@ -228,7 +231,7 @@ export class BenchmarkService { endDate?: Date; startDate: Date; userCurrency: string; - } & UniqueAsset): Promise { + } & AssetProfileIdentifier): Promise { const marketData: { date: string; value: number }[] = []; const days = differenceInDays(endDate, startDate) + 1; @@ -348,7 +351,7 @@ export class BenchmarkService { public async addBenchmark({ dataSource, symbol - }: UniqueAsset): Promise> { + }: AssetProfileIdentifier): Promise> { const assetProfile = await this.prismaService.symbolProfile.findFirst({ where: { dataSource, @@ -385,7 +388,7 @@ export class BenchmarkService { public async deleteBenchmark({ dataSource, symbol - }: UniqueAsset): Promise> { + }: AssetProfileIdentifier): Promise> { const assetProfile = await this.prismaService.symbolProfile.findFirst({ where: { dataSource, diff --git a/apps/api/src/app/import/import.service.ts b/apps/api/src/app/import/import.service.ts index c5809096e..69e64387d 100644 --- a/apps/api/src/app/import/import.service.ts +++ b/apps/api/src/app/import/import.service.ts @@ -19,7 +19,7 @@ import { getAssetProfileIdentifier, parseDate } from '@ghostfolio/common/helper'; -import { UniqueAsset } from '@ghostfolio/common/interfaces'; +import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces'; import { AccountWithPlatform, OrderWithAccount, @@ -51,7 +51,7 @@ export class ImportService { dataSource, symbol, userCurrency - }: UniqueAsset & { userCurrency: string }): Promise { + }: AssetProfileIdentifier & { userCurrency: string }): Promise { try { const { firstBuyDate, historicalData, orders } = await this.portfolioService.getPosition(dataSource, undefined, symbol); diff --git a/apps/api/src/app/logo/logo.service.ts b/apps/api/src/app/logo/logo.service.ts index a944900a0..908921a19 100644 --- a/apps/api/src/app/logo/logo.service.ts +++ b/apps/api/src/app/logo/logo.service.ts @@ -1,6 +1,6 @@ import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/symbol-profile.service'; -import { UniqueAsset } from '@ghostfolio/common/interfaces'; +import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces'; import { HttpException, Injectable } from '@nestjs/common'; import { DataSource } from '@prisma/client'; @@ -17,7 +17,7 @@ export class LogoService { public async getLogoByDataSourceAndSymbol({ dataSource, symbol - }: UniqueAsset) { + }: AssetProfileIdentifier) { if (!DataSource[dataSource]) { throw new HttpException( getReasonPhrase(StatusCodes.NOT_FOUND), diff --git a/apps/api/src/app/order/order.service.ts b/apps/api/src/app/order/order.service.ts index f7be3ba00..3b95211f0 100644 --- a/apps/api/src/app/order/order.service.ts +++ b/apps/api/src/app/order/order.service.ts @@ -11,9 +11,9 @@ import { } from '@ghostfolio/common/config'; import { getAssetProfileIdentifier } from '@ghostfolio/common/helper'; import { + AssetProfileIdentifier, EnhancedSymbolProfile, - Filter, - UniqueAsset + Filter } from '@ghostfolio/common/interfaces'; import { OrderWithAccount } from '@ghostfolio/common/types'; @@ -51,7 +51,7 @@ export class OrderService { symbol, tags, userId - }: { tags: Tag[]; userId: string } & UniqueAsset) { + }: { tags: Tag[]; userId: string } & AssetProfileIdentifier) { const orders = await this.prismaService.order.findMany({ where: { userId, @@ -285,7 +285,7 @@ export class OrderService { return count; } - public async getLatestOrder({ dataSource, symbol }: UniqueAsset) { + public async getLatestOrder({ dataSource, symbol }: AssetProfileIdentifier) { return this.prismaService.order.findFirst({ orderBy: { date: 'desc' @@ -464,7 +464,7 @@ export class OrderService { this.prismaService.order.count({ where }) ]); - const uniqueAssets = uniqBy( + const assetProfileIdentifiers = uniqBy( orders.map(({ SymbolProfile }) => { return { dataSource: SymbolProfile.dataSource, @@ -479,8 +479,9 @@ export class OrderService { } ); - const assetProfiles = - await this.symbolProfileService.getSymbolProfiles(uniqueAssets); + const assetProfiles = await this.symbolProfileService.getSymbolProfiles( + assetProfileIdentifiers + ); const activities = orders.map((order) => { const assetProfile = assetProfiles.find(({ dataSource, symbol }) => { diff --git a/apps/api/src/app/portfolio/calculator/mwr/portfolio-calculator.ts b/apps/api/src/app/portfolio/calculator/mwr/portfolio-calculator.ts index 5d168b619..1b142d8b3 100644 --- a/apps/api/src/app/portfolio/calculator/mwr/portfolio-calculator.ts +++ b/apps/api/src/app/portfolio/calculator/mwr/portfolio-calculator.ts @@ -1,5 +1,8 @@ import { PortfolioCalculator } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator'; -import { SymbolMetrics, UniqueAsset } from '@ghostfolio/common/interfaces'; +import { + AssetProfileIdentifier, + SymbolMetrics +} from '@ghostfolio/common/interfaces'; import { PortfolioSnapshot, TimelinePosition } from '@ghostfolio/common/models'; export class MWRPortfolioCalculator extends PortfolioCalculator { @@ -27,7 +30,7 @@ export class MWRPortfolioCalculator extends PortfolioCalculator { }; start: Date; step?: number; - } & UniqueAsset): SymbolMetrics { + } & AssetProfileIdentifier): SymbolMetrics { throw new Error('Method not implemented.'); } } diff --git a/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts b/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts index ec56a247c..8c9342e39 100644 --- a/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts +++ b/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts @@ -19,12 +19,12 @@ import { resetHours } from '@ghostfolio/common/helper'; import { + AssetProfileIdentifier, DataProviderInfo, HistoricalDataItem, InvestmentItem, ResponseError, - SymbolMetrics, - UniqueAsset + SymbolMetrics } from '@ghostfolio/common/interfaces'; import { PortfolioSnapshot, TimelinePosition } from '@ghostfolio/common/models'; import { DateRange, GroupBy } from '@ghostfolio/common/types'; @@ -356,15 +356,15 @@ export abstract class PortfolioCalculator { dataSource: item.dataSource, fee: item.fee, firstBuyDate: item.firstBuyDate, - grossPerformance: !hasErrors ? grossPerformance ?? null : null, + grossPerformance: !hasErrors ? (grossPerformance ?? null) : null, grossPerformancePercentage: !hasErrors - ? grossPerformancePercentage ?? null + ? (grossPerformancePercentage ?? null) : null, grossPerformancePercentageWithCurrencyEffect: !hasErrors - ? grossPerformancePercentageWithCurrencyEffect ?? null + ? (grossPerformancePercentageWithCurrencyEffect ?? null) : null, grossPerformanceWithCurrencyEffect: !hasErrors - ? grossPerformanceWithCurrencyEffect ?? null + ? (grossPerformanceWithCurrencyEffect ?? null) : null, investment: totalInvestment, investmentWithCurrencyEffect: totalInvestmentWithCurrencyEffect, @@ -372,15 +372,15 @@ export abstract class PortfolioCalculator { marketSymbolMap[endDateString]?.[item.symbol]?.toNumber() ?? null, marketPriceInBaseCurrency: marketPriceInBaseCurrency?.toNumber() ?? null, - netPerformance: !hasErrors ? netPerformance ?? null : null, + netPerformance: !hasErrors ? (netPerformance ?? null) : null, netPerformancePercentage: !hasErrors - ? netPerformancePercentage ?? null + ? (netPerformancePercentage ?? null) : null, netPerformancePercentageWithCurrencyEffect: !hasErrors - ? netPerformancePercentageWithCurrencyEffect ?? null + ? (netPerformancePercentageWithCurrencyEffect ?? null) : null, netPerformanceWithCurrencyEffect: !hasErrors - ? netPerformanceWithCurrencyEffect ?? null + ? (netPerformanceWithCurrencyEffect ?? null) : null, quantity: item.quantity, symbol: item.symbol, @@ -905,7 +905,7 @@ export abstract class PortfolioCalculator { }; start: Date; step?: number; - } & UniqueAsset): SymbolMetrics; + } & AssetProfileIdentifier): SymbolMetrics; public getTransactionPoints() { return this.transactionPoints; diff --git a/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator.ts b/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator.ts index e9f1f3fda..50550eec5 100644 --- a/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator.ts +++ b/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator.ts @@ -2,7 +2,10 @@ import { PortfolioCalculator } from '@ghostfolio/api/app/portfolio/calculator/po import { PortfolioOrderItem } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-order-item.interface'; import { getFactor } from '@ghostfolio/api/helper/portfolio.helper'; import { DATE_FORMAT } from '@ghostfolio/common/helper'; -import { SymbolMetrics, UniqueAsset } from '@ghostfolio/common/interfaces'; +import { + AssetProfileIdentifier, + SymbolMetrics +} from '@ghostfolio/common/interfaces'; import { PortfolioSnapshot, TimelinePosition } from '@ghostfolio/common/models'; import { Logger } from '@nestjs/common'; @@ -151,7 +154,7 @@ export class TWRPortfolioCalculator extends PortfolioCalculator { }; start: Date; step?: number; - } & UniqueAsset): SymbolMetrics { + } & AssetProfileIdentifier): SymbolMetrics { const currentExchangeRate = exchangeRates[format(new Date(), DATE_FORMAT)]; const currentValues: { [date: string]: Big } = {}; const currentValuesWithCurrencyEffect: { [date: string]: Big } = {}; diff --git a/apps/api/src/app/portfolio/current-rate.service.spec.ts b/apps/api/src/app/portfolio/current-rate.service.spec.ts index 9b0548522..c86dde448 100644 --- a/apps/api/src/app/portfolio/current-rate.service.spec.ts +++ b/apps/api/src/app/portfolio/current-rate.service.spec.ts @@ -1,7 +1,7 @@ import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service'; import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service'; import { PropertyService } from '@ghostfolio/api/services/property/property.service'; -import { UniqueAsset } from '@ghostfolio/common/interfaces'; +import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces'; import { DataSource, MarketData } from '@prisma/client'; @@ -24,32 +24,32 @@ jest.mock('@ghostfolio/api/services/market-data/market-data.service', () => { }); }, getRange: ({ + assetProfileIdentifiers, dateRangeEnd, - dateRangeStart, - uniqueAssets + dateRangeStart }: { + assetProfileIdentifiers: AssetProfileIdentifier[]; dateRangeEnd: Date; dateRangeStart: Date; - uniqueAssets: UniqueAsset[]; }) => { return Promise.resolve([ { createdAt: dateRangeStart, - dataSource: uniqueAssets[0].dataSource, + dataSource: assetProfileIdentifiers[0].dataSource, date: dateRangeStart, id: '8fa48fde-f397-4b0d-adbc-fb940e830e6d', marketPrice: 1841.823902, state: 'CLOSE', - symbol: uniqueAssets[0].symbol + symbol: assetProfileIdentifiers[0].symbol }, { createdAt: dateRangeEnd, - dataSource: uniqueAssets[0].dataSource, + dataSource: assetProfileIdentifiers[0].dataSource, date: dateRangeEnd, id: '082d6893-df27-4c91-8a5d-092e84315b56', marketPrice: 1847.839966, state: 'CLOSE', - symbol: uniqueAssets[0].symbol + symbol: assetProfileIdentifiers[0].symbol } ]); } diff --git a/apps/api/src/app/portfolio/current-rate.service.ts b/apps/api/src/app/portfolio/current-rate.service.ts index 712d07e7a..24119162d 100644 --- a/apps/api/src/app/portfolio/current-rate.service.ts +++ b/apps/api/src/app/portfolio/current-rate.service.ts @@ -3,9 +3,9 @@ import { DataProviderService } from '@ghostfolio/api/services/data-provider/data import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service'; import { resetHours } from '@ghostfolio/common/helper'; import { + AssetProfileIdentifier, DataProviderInfo, - ResponseError, - UniqueAsset + ResponseError } from '@ghostfolio/common/interfaces'; import type { RequestWithUser } from '@ghostfolio/common/types'; @@ -80,17 +80,16 @@ export class CurrentRateService { ); } - const uniqueAssets: UniqueAsset[] = dataGatheringItems.map( - ({ dataSource, symbol }) => { + const assetProfileIdentifiers: AssetProfileIdentifier[] = + dataGatheringItems.map(({ dataSource, symbol }) => { return { dataSource, symbol }; - } - ); + }); promises.push( this.marketDataService .getRange({ - dateQuery, - uniqueAssets + assetProfileIdentifiers, + dateQuery }) .then((data) => { return data.map(({ dataSource, date, marketPrice, symbol }) => { diff --git a/apps/api/src/app/portfolio/interfaces/get-value-object.interface.ts b/apps/api/src/app/portfolio/interfaces/get-value-object.interface.ts index 6c42d260c..34b426693 100644 --- a/apps/api/src/app/portfolio/interfaces/get-value-object.interface.ts +++ b/apps/api/src/app/portfolio/interfaces/get-value-object.interface.ts @@ -1,6 +1,6 @@ -import { UniqueAsset } from '@ghostfolio/common/interfaces'; +import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces'; -export interface GetValueObject extends UniqueAsset { +export interface GetValueObject extends AssetProfileIdentifier { date: Date; marketPrice: number; } diff --git a/apps/api/src/app/redis-cache/redis-cache.service.ts b/apps/api/src/app/redis-cache/redis-cache.service.ts index 53b177b4f..de41220b9 100644 --- a/apps/api/src/app/redis-cache/redis-cache.service.ts +++ b/apps/api/src/app/redis-cache/redis-cache.service.ts @@ -1,6 +1,6 @@ import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { getAssetProfileIdentifier } from '@ghostfolio/common/helper'; -import { UniqueAsset } from '@ghostfolio/common/interfaces'; +import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces'; import { CACHE_MANAGER } from '@nestjs/cache-manager'; import { Inject, Injectable, Logger } from '@nestjs/common'; @@ -28,7 +28,7 @@ export class RedisCacheService { return `portfolio-snapshot-${userId}`; } - public getQuoteKey({ dataSource, symbol }: UniqueAsset) { + public getQuoteKey({ dataSource, symbol }: AssetProfileIdentifier) { return `quote-${getAssetProfileIdentifier({ dataSource, symbol })}`; } diff --git a/apps/api/src/app/symbol/interfaces/symbol-item.interface.ts b/apps/api/src/app/symbol/interfaces/symbol-item.interface.ts index 358658672..710a84144 100644 --- a/apps/api/src/app/symbol/interfaces/symbol-item.interface.ts +++ b/apps/api/src/app/symbol/interfaces/symbol-item.interface.ts @@ -1,6 +1,9 @@ -import { HistoricalDataItem, UniqueAsset } from '@ghostfolio/common/interfaces'; +import { + AssetProfileIdentifier, + HistoricalDataItem +} from '@ghostfolio/common/interfaces'; -export interface SymbolItem extends UniqueAsset { +export interface SymbolItem extends AssetProfileIdentifier { currency: string; historicalData: HistoricalDataItem[]; marketPrice: number; diff --git a/apps/api/src/app/symbol/symbol.service.ts b/apps/api/src/app/symbol/symbol.service.ts index 90259a776..2baca18dd 100644 --- a/apps/api/src/app/symbol/symbol.service.ts +++ b/apps/api/src/app/symbol/symbol.service.ts @@ -40,13 +40,13 @@ export class SymbolService { const days = includeHistoricalData; const marketData = await this.marketDataService.getRange({ - dateQuery: { gte: subDays(new Date(), days) }, - uniqueAssets: [ + assetProfileIdentifiers: [ { dataSource: dataGatheringItem.dataSource, symbol: dataGatheringItem.symbol } - ] + ], + dateQuery: { gte: subDays(new Date(), days) } }); historicalData = marketData.map(({ date, marketPrice: value }) => { diff --git a/apps/api/src/services/data-gathering/data-gathering.processor.ts b/apps/api/src/services/data-gathering/data-gathering.processor.ts index 11eda2e7a..d8a6a7644 100644 --- a/apps/api/src/services/data-gathering/data-gathering.processor.ts +++ b/apps/api/src/services/data-gathering/data-gathering.processor.ts @@ -7,7 +7,7 @@ import { GATHER_HISTORICAL_MARKET_DATA_PROCESS } from '@ghostfolio/common/config'; import { DATE_FORMAT, getStartOfUtcDate } from '@ghostfolio/common/helper'; -import { UniqueAsset } from '@ghostfolio/common/interfaces'; +import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces'; import { Process, Processor } from '@nestjs/bull'; import { Injectable, Logger } from '@nestjs/common'; @@ -35,7 +35,7 @@ export class DataGatheringProcessor { ) {} @Process({ concurrency: 1, name: GATHER_ASSET_PROFILE_PROCESS }) - public async gatherAssetProfile(job: Job) { + public async gatherAssetProfile(job: Job) { try { Logger.log( `Asset profile data gathering has been started for ${job.data.symbol} (${job.data.dataSource})`, diff --git a/apps/api/src/services/data-gathering/data-gathering.service.ts b/apps/api/src/services/data-gathering/data-gathering.service.ts index 2bf6cc1b2..8b8c65a21 100644 --- a/apps/api/src/services/data-gathering/data-gathering.service.ts +++ b/apps/api/src/services/data-gathering/data-gathering.service.ts @@ -20,7 +20,10 @@ import { getAssetProfileIdentifier, resetHours } from '@ghostfolio/common/helper'; -import { BenchmarkProperty, UniqueAsset } from '@ghostfolio/common/interfaces'; +import { + AssetProfileIdentifier, + BenchmarkProperty +} from '@ghostfolio/common/interfaces'; import { InjectQueue } from '@nestjs/bull'; import { Inject, Injectable, Logger } from '@nestjs/common'; @@ -91,7 +94,7 @@ export class DataGatheringService { }); } - public async gatherSymbol({ dataSource, symbol }: UniqueAsset) { + public async gatherSymbol({ dataSource, symbol }: AssetProfileIdentifier) { await this.marketDataService.deleteMany({ dataSource, symbol }); const dataGatheringItems = (await this.getSymbolsMax()).filter( @@ -146,23 +149,29 @@ export class DataGatheringService { } } - public async gatherAssetProfiles(aUniqueAssets?: UniqueAsset[]) { - let uniqueAssets = aUniqueAssets?.filter((dataGatheringItem) => { - return dataGatheringItem.dataSource !== 'MANUAL'; - }); + public async gatherAssetProfiles( + aAssetProfileIdentifiers?: AssetProfileIdentifier[] + ) { + let assetProfileIdentifiers = aAssetProfileIdentifiers?.filter( + (dataGatheringItem) => { + return dataGatheringItem.dataSource !== 'MANUAL'; + } + ); - if (!uniqueAssets) { - uniqueAssets = await this.getAllAssetProfileIdentifiers(); + if (!assetProfileIdentifiers) { + assetProfileIdentifiers = await this.getAllAssetProfileIdentifiers(); } - if (uniqueAssets.length <= 0) { + if (assetProfileIdentifiers.length <= 0) { return; } - const assetProfiles = - await this.dataProviderService.getAssetProfiles(uniqueAssets); - const symbolProfiles = - await this.symbolProfileService.getSymbolProfiles(uniqueAssets); + const assetProfiles = await this.dataProviderService.getAssetProfiles( + assetProfileIdentifiers + ); + const symbolProfiles = await this.symbolProfileService.getSymbolProfiles( + assetProfileIdentifiers + ); for (const [symbol, assetProfile] of Object.entries(assetProfiles)) { const symbolMapping = symbolProfiles.find((symbolProfile) => { @@ -248,7 +257,7 @@ export class DataGatheringService { 'DataGatheringService' ); - if (uniqueAssets.length === 1) { + if (assetProfileIdentifiers.length === 1) { throw error; } } @@ -284,7 +293,9 @@ export class DataGatheringService { ); } - public async getAllAssetProfileIdentifiers(): Promise { + public async getAllAssetProfileIdentifiers(): Promise< + AssetProfileIdentifier[] + > { const symbolProfiles = await this.prismaService.symbolProfile.findMany({ orderBy: [{ symbol: 'asc' }] }); @@ -305,7 +316,7 @@ export class DataGatheringService { } private async getAssetProfileIdentifiersWithCompleteMarketData(): Promise< - UniqueAsset[] + AssetProfileIdentifier[] > { return ( await this.prismaService.marketData.groupBy({ 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 58574a3b5..e5eda2d7e 100644 --- a/apps/api/src/services/data-provider/data-provider.service.ts +++ b/apps/api/src/services/data-provider/data-provider.service.ts @@ -20,7 +20,7 @@ import { getStartOfUtcDate, isDerivedCurrency } from '@ghostfolio/common/helper'; -import { UniqueAsset } from '@ghostfolio/common/interfaces'; +import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces'; import type { Granularity, UserWithSettings } from '@ghostfolio/common/types'; import { Inject, Injectable, Logger } from '@nestjs/common'; @@ -75,7 +75,7 @@ export class DataProviderService { return false; } - public async getAssetProfiles(items: UniqueAsset[]): Promise<{ + public async getAssetProfiles(items: AssetProfileIdentifier[]): Promise<{ [symbol: string]: Partial; }> { const response: { @@ -173,7 +173,7 @@ export class DataProviderService { } public async getHistorical( - aItems: UniqueAsset[], + aItems: AssetProfileIdentifier[], aGranularity: Granularity = 'month', from: Date, to: Date @@ -243,7 +243,7 @@ export class DataProviderService { from, to }: { - dataGatheringItems: UniqueAsset[]; + dataGatheringItems: AssetProfileIdentifier[]; from: Date; to: Date; }): Promise<{ @@ -350,7 +350,7 @@ export class DataProviderService { useCache = true, user }: { - items: UniqueAsset[]; + items: AssetProfileIdentifier[]; requestTimeout?: number; useCache?: boolean; user?: UserWithSettings; @@ -376,7 +376,7 @@ export class DataProviderService { } // Get items from cache - const itemsToFetch: UniqueAsset[] = []; + const itemsToFetch: AssetProfileIdentifier[] = []; for (const { dataSource, symbol } of items) { if (useCache) { @@ -633,7 +633,7 @@ export class DataProviderService { dataGatheringItems }: { currency: string; - dataGatheringItems: UniqueAsset[]; + dataGatheringItems: AssetProfileIdentifier[]; }) { return dataGatheringItems.some(({ dataSource, symbol }) => { return ( diff --git a/apps/api/src/services/exchange-rate-data/exchange-rate-data.service.ts b/apps/api/src/services/exchange-rate-data/exchange-rate-data.service.ts index 29d199ed7..1f08034cd 100644 --- a/apps/api/src/services/exchange-rate-data/exchange-rate-data.service.ts +++ b/apps/api/src/services/exchange-rate-data/exchange-rate-data.service.ts @@ -361,13 +361,13 @@ export class ExchangeRateDataService { const symbol = `${currencyFrom}${currencyTo}`; const marketData = await this.marketDataService.getRange({ - dateQuery: { gte: startDate, lt: endDate }, - uniqueAssets: [ + assetProfileIdentifiers: [ { dataSource, symbol } - ] + ], + dateQuery: { gte: startDate, lt: endDate } }); if (marketData?.length > 0) { @@ -392,13 +392,13 @@ export class ExchangeRateDataService { } } else { const marketData = await this.marketDataService.getRange({ - dateQuery: { gte: startDate, lt: endDate }, - uniqueAssets: [ + assetProfileIdentifiers: [ { dataSource, symbol: `${DEFAULT_CURRENCY}${currencyFrom}` } - ] + ], + dateQuery: { gte: startDate, lt: endDate } }); for (const { date, marketPrice } of marketData) { @@ -415,16 +415,16 @@ export class ExchangeRateDataService { } } else { const marketData = await this.marketDataService.getRange({ - dateQuery: { - gte: startDate, - lt: endDate - }, - uniqueAssets: [ + assetProfileIdentifiers: [ { dataSource, symbol: `${DEFAULT_CURRENCY}${currencyTo}` } - ] + ], + dateQuery: { + gte: startDate, + lt: endDate + } }); for (const { date, marketPrice } of marketData) { diff --git a/apps/api/src/services/interfaces/interfaces.ts b/apps/api/src/services/interfaces/interfaces.ts index b945d0945..fa7fc4d09 100644 --- a/apps/api/src/services/interfaces/interfaces.ts +++ b/apps/api/src/services/interfaces/interfaces.ts @@ -1,4 +1,7 @@ -import { DataProviderInfo, UniqueAsset } from '@ghostfolio/common/interfaces'; +import { + AssetProfileIdentifier, + DataProviderInfo +} from '@ghostfolio/common/interfaces'; import { MarketState } from '@ghostfolio/common/types'; import { @@ -34,6 +37,6 @@ export interface IDataProviderResponse { marketState: MarketState; } -export interface IDataGatheringItem extends UniqueAsset { +export interface IDataGatheringItem extends AssetProfileIdentifier { date?: Date; } diff --git a/apps/api/src/services/market-data/market-data.service.ts b/apps/api/src/services/market-data/market-data.service.ts index faf429955..09f591b9e 100644 --- a/apps/api/src/services/market-data/market-data.service.ts +++ b/apps/api/src/services/market-data/market-data.service.ts @@ -3,7 +3,7 @@ import { DateQuery } from '@ghostfolio/api/app/portfolio/interfaces/date-query.i import { IDataGatheringItem } from '@ghostfolio/api/services/interfaces/interfaces'; import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service'; import { resetHours } from '@ghostfolio/common/helper'; -import { UniqueAsset } from '@ghostfolio/common/interfaces'; +import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces'; import { Injectable } from '@nestjs/common'; import { @@ -17,7 +17,7 @@ import { export class MarketDataService { public constructor(private readonly prismaService: PrismaService) {} - public async deleteMany({ dataSource, symbol }: UniqueAsset) { + public async deleteMany({ dataSource, symbol }: AssetProfileIdentifier) { return this.prismaService.marketData.deleteMany({ where: { dataSource, @@ -40,7 +40,7 @@ export class MarketDataService { }); } - public async getMax({ dataSource, symbol }: UniqueAsset) { + public async getMax({ dataSource, symbol }: AssetProfileIdentifier) { return this.prismaService.marketData.findFirst({ select: { date: true, @@ -59,11 +59,11 @@ export class MarketDataService { } public async getRange({ - dateQuery, - uniqueAssets + assetProfileIdentifiers, + dateQuery }: { + assetProfileIdentifiers: AssetProfileIdentifier[]; dateQuery: DateQuery; - uniqueAssets: UniqueAsset[]; }): Promise { return this.prismaService.marketData.findMany({ orderBy: [ @@ -76,13 +76,13 @@ export class MarketDataService { ], where: { dataSource: { - in: uniqueAssets.map(({ dataSource }) => { + in: assetProfileIdentifiers.map(({ dataSource }) => { return dataSource; }) }, date: dateQuery, symbol: { - in: uniqueAssets.map(({ symbol }) => { + in: assetProfileIdentifiers.map(({ symbol }) => { return symbol; }) } diff --git a/apps/api/src/services/symbol-profile/symbol-profile.service.ts b/apps/api/src/services/symbol-profile/symbol-profile.service.ts index e0cfed292..50cb25000 100644 --- a/apps/api/src/services/symbol-profile/symbol-profile.service.ts +++ b/apps/api/src/services/symbol-profile/symbol-profile.service.ts @@ -1,10 +1,10 @@ import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service'; import { UNKNOWN_KEY } from '@ghostfolio/common/config'; import { + AssetProfileIdentifier, EnhancedSymbolProfile, Holding, - ScraperConfiguration, - UniqueAsset + ScraperConfiguration } from '@ghostfolio/common/interfaces'; import { Country } from '@ghostfolio/common/interfaces/country.interface'; import { Sector } from '@ghostfolio/common/interfaces/sector.interface'; @@ -23,7 +23,7 @@ export class SymbolProfileService { return this.prismaService.symbolProfile.create({ data: assetProfile }); } - public async delete({ dataSource, symbol }: UniqueAsset) { + public async delete({ dataSource, symbol }: AssetProfileIdentifier) { return this.prismaService.symbolProfile.delete({ where: { dataSource_symbol: { dataSource, symbol } } }); @@ -36,7 +36,7 @@ export class SymbolProfileService { } public async getSymbolProfiles( - aUniqueAssets: UniqueAsset[] + aAssetProfileIdentifiers: AssetProfileIdentifier[] ): Promise { return this.prismaService.symbolProfile .findMany({ @@ -54,7 +54,7 @@ export class SymbolProfileService { SymbolProfileOverrides: true }, where: { - OR: aUniqueAssets.map(({ dataSource, symbol }) => { + OR: aAssetProfileIdentifiers.map(({ dataSource, symbol }) => { return { dataSource, symbol @@ -140,7 +140,7 @@ export class SymbolProfileService { symbolMapping, SymbolProfileOverrides, url - }: Prisma.SymbolProfileUpdateInput & UniqueAsset) { + }: AssetProfileIdentifier & Prisma.SymbolProfileUpdateInput) { return this.prismaService.symbolProfile.update({ data: { assetClass, diff --git a/apps/client/src/app/components/admin-market-data/admin-market-data.component.ts b/apps/client/src/app/components/admin-market-data/admin-market-data.component.ts index e27283517..98a1d0480 100644 --- a/apps/client/src/app/components/admin-market-data/admin-market-data.component.ts +++ b/apps/client/src/app/components/admin-market-data/admin-market-data.component.ts @@ -7,9 +7,9 @@ import { } from '@ghostfolio/common/config'; import { getDateFormatString } from '@ghostfolio/common/helper'; import { + AssetProfileIdentifier, Filter, InfoItem, - UniqueAsset, User } from '@ghostfolio/common/interfaces'; import { AdminMarketDataItem } from '@ghostfolio/common/interfaces/admin-market-data.interface'; @@ -225,7 +225,7 @@ export class AdminMarketDataComponent }); } - public onDeleteAssetProfile({ dataSource, symbol }: UniqueAsset) { + public onDeleteAssetProfile({ dataSource, symbol }: AssetProfileIdentifier) { this.adminMarketDataService.deleteAssetProfile({ dataSource, symbol }); } @@ -266,21 +266,27 @@ export class AdminMarketDataComponent .subscribe(() => {}); } - public onGatherProfileDataBySymbol({ dataSource, symbol }: UniqueAsset) { + public onGatherProfileDataBySymbol({ + dataSource, + symbol + }: AssetProfileIdentifier) { this.adminService .gatherProfileDataBySymbol({ dataSource, symbol }) .pipe(takeUntil(this.unsubscribeSubject)) .subscribe(() => {}); } - public onGatherSymbol({ dataSource, symbol }: UniqueAsset) { + public onGatherSymbol({ dataSource, symbol }: AssetProfileIdentifier) { this.adminService .gatherSymbol({ dataSource, symbol }) .pipe(takeUntil(this.unsubscribeSubject)) .subscribe(() => {}); } - public onOpenAssetProfileDialog({ dataSource, symbol }: UniqueAsset) { + public onOpenAssetProfileDialog({ + dataSource, + symbol + }: AssetProfileIdentifier) { this.router.navigate([], { queryParams: { dataSource, diff --git a/apps/client/src/app/components/admin-market-data/admin-market-data.service.ts b/apps/client/src/app/components/admin-market-data/admin-market-data.service.ts index 8f3084cd8..799606293 100644 --- a/apps/client/src/app/components/admin-market-data/admin-market-data.service.ts +++ b/apps/client/src/app/components/admin-market-data/admin-market-data.service.ts @@ -2,8 +2,8 @@ import { AdminService } from '@ghostfolio/client/services/admin.service'; import { ghostfolioScraperApiSymbolPrefix } from '@ghostfolio/common/config'; import { getCurrencyFromSymbol, isCurrency } from '@ghostfolio/common/helper'; import { - AdminMarketDataItem, - UniqueAsset + AssetProfileIdentifier, + AdminMarketDataItem } from '@ghostfolio/common/interfaces'; import { Injectable } from '@angular/core'; @@ -13,7 +13,7 @@ import { EMPTY, catchError, finalize, forkJoin, takeUntil } from 'rxjs'; export class AdminMarketDataService { public constructor(private adminService: AdminService) {} - public deleteAssetProfile({ dataSource, symbol }: UniqueAsset) { + public deleteAssetProfile({ dataSource, symbol }: AssetProfileIdentifier) { const confirmation = confirm( $localize`Do you really want to delete this asset profile?` ); @@ -29,15 +29,19 @@ export class AdminMarketDataService { } } - public deleteAssetProfiles(uniqueAssets: UniqueAsset[]) { + public deleteAssetProfiles( + aAssetProfileIdentifiers: AssetProfileIdentifier[] + ) { const confirmation = confirm( $localize`Do you really want to delete these profiles?` ); if (confirmation) { - const deleteRequests = uniqueAssets.map(({ dataSource, symbol }) => { - return this.adminService.deleteProfileData({ dataSource, symbol }); - }); + const deleteRequests = aAssetProfileIdentifiers.map( + ({ dataSource, symbol }) => { + return this.adminService.deleteProfileData({ dataSource, symbol }); + } + ); forkJoin(deleteRequests) .pipe( diff --git a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts index 18b73a0cb..a24d6dc30 100644 --- a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts +++ b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts @@ -8,7 +8,7 @@ import { ghostfolioScraperApiSymbolPrefix } from '@ghostfolio/common/config'; import { DATE_FORMAT } from '@ghostfolio/common/helper'; import { AdminMarketDataDetails, - UniqueAsset + AssetProfileIdentifier } from '@ghostfolio/common/interfaces'; import { translate } from '@ghostfolio/ui/i18n'; @@ -175,20 +175,23 @@ export class AssetProfileDialog implements OnDestroy, OnInit { this.dialogRef.close(); } - public onDeleteProfileData({ dataSource, symbol }: UniqueAsset) { + public onDeleteProfileData({ dataSource, symbol }: AssetProfileIdentifier) { this.adminMarketDataService.deleteAssetProfile({ dataSource, symbol }); this.dialogRef.close(); } - public onGatherProfileDataBySymbol({ dataSource, symbol }: UniqueAsset) { + public onGatherProfileDataBySymbol({ + dataSource, + symbol + }: AssetProfileIdentifier) { this.adminService .gatherProfileDataBySymbol({ dataSource, symbol }) .pipe(takeUntil(this.unsubscribeSubject)) .subscribe(() => {}); } - public onGatherSymbol({ dataSource, symbol }: UniqueAsset) { + public onGatherSymbol({ dataSource, symbol }: AssetProfileIdentifier) { this.adminService .gatherSymbol({ dataSource, symbol }) .pipe(takeUntil(this.unsubscribeSubject)) @@ -242,7 +245,7 @@ export class AssetProfileDialog implements OnDestroy, OnInit { } } - public onSetBenchmark({ dataSource, symbol }: UniqueAsset) { + public onSetBenchmark({ dataSource, symbol }: AssetProfileIdentifier) { this.dataService .postBenchmark({ dataSource, symbol }) .pipe(takeUntil(this.unsubscribeSubject)) @@ -342,7 +345,7 @@ export class AssetProfileDialog implements OnDestroy, OnInit { }); } - public onUnsetBenchmark({ dataSource, symbol }: UniqueAsset) { + public onUnsetBenchmark({ dataSource, symbol }: AssetProfileIdentifier) { this.dataService .deleteBenchmark({ dataSource, symbol }) .pipe(takeUntil(this.unsubscribeSubject)) diff --git a/apps/client/src/app/components/home-holdings/home-holdings.component.ts b/apps/client/src/app/components/home-holdings/home-holdings.component.ts index dca8bbe55..fc51796d6 100644 --- a/apps/client/src/app/components/home-holdings/home-holdings.component.ts +++ b/apps/client/src/app/components/home-holdings/home-holdings.component.ts @@ -2,8 +2,8 @@ import { DataService } from '@ghostfolio/client/services/data.service'; import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; import { + AssetProfileIdentifier, PortfolioPosition, - UniqueAsset, User } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; @@ -108,7 +108,7 @@ export class HomeHoldingsComponent implements OnDestroy, OnInit { this.initialize(); } - public onSymbolClicked({ dataSource, symbol }: UniqueAsset) { + public onSymbolClicked({ dataSource, symbol }: AssetProfileIdentifier) { if (dataSource && symbol) { this.router.navigate([], { queryParams: { dataSource, symbol, holdingDetailDialog: true } diff --git a/apps/client/src/app/components/home-overview/home-overview.component.ts b/apps/client/src/app/components/home-overview/home-overview.component.ts index d07ca91ce..9addc24b7 100644 --- a/apps/client/src/app/components/home-overview/home-overview.component.ts +++ b/apps/client/src/app/components/home-overview/home-overview.component.ts @@ -5,9 +5,9 @@ import { ImpersonationStorageService } from '@ghostfolio/client/services/imperso import { UserService } from '@ghostfolio/client/services/user/user.service'; import { NUMERICAL_PRECISION_THRESHOLD } from '@ghostfolio/common/config'; import { + AssetProfileIdentifier, LineChartItem, PortfolioPerformance, - UniqueAsset, User } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; @@ -26,7 +26,7 @@ import { takeUntil } from 'rxjs/operators'; export class HomeOverviewComponent implements OnDestroy, OnInit { public dateRangeOptions = ToggleComponent.DEFAULT_DATE_RANGE_OPTIONS; public deviceType: string; - public errors: UniqueAsset[]; + public errors: AssetProfileIdentifier[]; public hasError: boolean; public hasImpersonationId: boolean; public hasPermissionToCreateOrder: boolean; diff --git a/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts b/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts index b59994811..1848c5306 100644 --- a/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts +++ b/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts @@ -38,6 +38,7 @@ import { ImportActivitiesDialogParams } from './interfaces/interfaces'; export class ImportActivitiesDialog implements OnDestroy { public accounts: CreateAccountDto[] = []; public activities: Activity[] = []; + public assetProfileForm: FormGroup; public dataSource: MatTableDataSource; public details: any[] = []; public deviceType: string; @@ -53,7 +54,6 @@ export class ImportActivitiesDialog implements OnDestroy { public sortDirection: SortDirection = 'desc'; public stepperOrientation: StepperOrientation; public totalItems: number; - public uniqueAssetForm: FormGroup; private unsubscribeSubject = new Subject(); @@ -73,8 +73,8 @@ export class ImportActivitiesDialog implements OnDestroy { this.stepperOrientation = this.deviceType === 'mobile' ? 'vertical' : 'horizontal'; - this.uniqueAssetForm = this.formBuilder.group({ - uniqueAsset: [undefined, Validators.required] + this.assetProfileForm = this.formBuilder.group({ + assetProfileIdentifier: [undefined, Validators.required] }); if ( @@ -85,7 +85,7 @@ export class ImportActivitiesDialog implements OnDestroy { this.dialogTitle = $localize`Import Dividends`; this.mode = 'DIVIDEND'; - this.uniqueAssetForm.get('uniqueAsset').disable(); + this.assetProfileForm.get('assetProfileIdentifier').disable(); this.dataService .fetchPortfolioHoldings({ @@ -102,7 +102,7 @@ export class ImportActivitiesDialog implements OnDestroy { this.holdings = sortBy(holdings, ({ name }) => { return name.toLowerCase(); }); - this.uniqueAssetForm.get('uniqueAsset').enable(); + this.assetProfileForm.get('assetProfileIdentifier').enable(); this.isLoading = false; @@ -167,10 +167,11 @@ export class ImportActivitiesDialog implements OnDestroy { } public onLoadDividends(aStepper: MatStepper) { - this.uniqueAssetForm.get('uniqueAsset').disable(); + this.assetProfileForm.get('assetProfileIdentifier').disable(); - const { dataSource, symbol } = - this.uniqueAssetForm.get('uniqueAsset').value; + const { dataSource, symbol } = this.assetProfileForm.get( + 'assetProfileIdentifier' + ).value; this.dataService .fetchDividendsImport({ @@ -193,7 +194,7 @@ export class ImportActivitiesDialog implements OnDestroy { this.details = []; this.errorMessages = []; this.importStep = ImportStep.SELECT_ACTIVITIES; - this.uniqueAssetForm.get('uniqueAsset').enable(); + this.assetProfileForm.get('assetProfileIdentifier').enable(); aStepper.reset(); } diff --git a/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.html b/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.html index d8c2201f9..3bffe8aee 100644 --- a/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.html +++ b/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.html @@ -25,14 +25,14 @@
@if (mode === 'DIVIDEND') {
Holding - + {{ - uniqueAssetForm.get('uniqueAsset')?.value?.name + assetProfileForm.get('assetProfileIdentifier')?.value?.name }} @for (holding of holdings; track holding) { Load Dividends diff --git a/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts b/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts index 27c6326e9..5af3a3099 100644 --- a/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts +++ b/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts @@ -6,10 +6,10 @@ import { UserService } from '@ghostfolio/client/services/user/user.service'; import { MAX_TOP_HOLDINGS, UNKNOWN_KEY } from '@ghostfolio/common/config'; import { prettifySymbol } from '@ghostfolio/common/helper'; import { + AssetProfileIdentifier, Holding, PortfolioDetails, PortfolioPosition, - UniqueAsset, User } from '@ghostfolio/common/interfaces'; import { Market, MarketAdvanced } from '@ghostfolio/common/types'; @@ -161,7 +161,7 @@ export class AllocationsPageComponent implements OnDestroy, OnInit { this.initialize(); } - public onAccountChartClicked({ symbol }: UniqueAsset) { + public onAccountChartClicked({ symbol }: AssetProfileIdentifier) { if (symbol && symbol !== UNKNOWN_KEY) { this.router.navigate([], { queryParams: { accountId: symbol, accountDetailDialog: true } @@ -169,7 +169,7 @@ export class AllocationsPageComponent implements OnDestroy, OnInit { } } - public onSymbolChartClicked({ dataSource, symbol }: UniqueAsset) { + public onSymbolChartClicked({ dataSource, symbol }: AssetProfileIdentifier) { if (dataSource && symbol) { this.router.navigate([], { queryParams: { dataSource, symbol, holdingDetailDialog: true } diff --git a/apps/client/src/app/services/admin.service.ts b/apps/client/src/app/services/admin.service.ts index 5bc281900..e5ea176d1 100644 --- a/apps/client/src/app/services/admin.service.ts +++ b/apps/client/src/app/services/admin.service.ts @@ -7,13 +7,13 @@ import { UpdateTagDto } from '@ghostfolio/api/app/tag/update-tag.dto'; import { IDataProviderHistoricalResponse } from '@ghostfolio/api/services/interfaces/interfaces'; import { DATE_FORMAT } from '@ghostfolio/common/helper'; import { + AssetProfileIdentifier, AdminData, AdminJobs, AdminMarketData, AdminMarketDataDetails, EnhancedSymbolProfile, - Filter, - UniqueAsset + Filter } from '@ghostfolio/common/interfaces'; import { HttpClient, HttpParams } from '@angular/common/http'; @@ -35,7 +35,7 @@ export class AdminService { private http: HttpClient ) {} - public addAssetProfile({ dataSource, symbol }: UniqueAsset) { + public addAssetProfile({ dataSource, symbol }: AssetProfileIdentifier) { return this.http.post( `/api/v1/admin/profile-data/${dataSource}/${symbol}`, null @@ -62,7 +62,7 @@ export class AdminService { return this.http.delete(`/api/v1/platform/${aId}`); } - public deleteProfileData({ dataSource, symbol }: UniqueAsset) { + public deleteProfileData({ dataSource, symbol }: AssetProfileIdentifier) { return this.http.delete( `/api/v1/admin/profile-data/${dataSource}/${symbol}` ); @@ -167,7 +167,10 @@ export class AdminService { return this.http.post('/api/v1/admin/gather/profile-data', {}); } - public gatherProfileDataBySymbol({ dataSource, symbol }: UniqueAsset) { + public gatherProfileDataBySymbol({ + dataSource, + symbol + }: AssetProfileIdentifier) { return this.http.post( `/api/v1/admin/gather/profile-data/${dataSource}/${symbol}`, {} @@ -178,7 +181,7 @@ export class AdminService { dataSource, date, symbol - }: UniqueAsset & { + }: AssetProfileIdentifier & { date?: Date; }) { let url = `/api/v1/admin/gather/${dataSource}/${symbol}`; @@ -217,7 +220,7 @@ export class AdminService { symbol, symbolMapping, url - }: UniqueAsset & UpdateAssetProfileDto) { + }: AssetProfileIdentifier & UpdateAssetProfileDto) { return this.http.patch( `/api/v1/admin/profile-data/${dataSource}/${symbol}`, { @@ -272,7 +275,7 @@ export class AdminService { dataSource, scraperConfiguration, symbol - }: UniqueAsset & UpdateAssetProfileDto['scraperConfiguration']) { + }: AssetProfileIdentifier & UpdateAssetProfileDto['scraperConfiguration']) { return this.http.post( `/api/v1/admin/market-data/${dataSource}/${symbol}/test`, { diff --git a/apps/client/src/app/services/data.service.ts b/apps/client/src/app/services/data.service.ts index 4f9fd7e20..e74c3f74e 100644 --- a/apps/client/src/app/services/data.service.ts +++ b/apps/client/src/app/services/data.service.ts @@ -20,6 +20,7 @@ import { AccountBalancesResponse, Accounts, AdminMarketDataDetails, + AssetProfileIdentifier, BenchmarkMarketDataDetails, BenchmarkResponse, Export, @@ -34,7 +35,6 @@ import { PortfolioPerformanceResponse, PortfolioPublicDetails, PortfolioReport, - UniqueAsset, User } from '@ghostfolio/common/interfaces'; import { filterGlobalPermissions } from '@ghostfolio/common/permissions'; @@ -230,7 +230,7 @@ export class DataService { }); } - public fetchDividendsImport({ dataSource, symbol }: UniqueAsset) { + public fetchDividendsImport({ dataSource, symbol }: AssetProfileIdentifier) { return this.http.get( `/api/v1/import/dividends/${dataSource}/${symbol}` ); @@ -270,7 +270,7 @@ export class DataService { return this.http.delete(`/api/v1/order/${aId}`); } - public deleteBenchmark({ dataSource, symbol }: UniqueAsset) { + public deleteBenchmark({ dataSource, symbol }: AssetProfileIdentifier) { return this.http.delete(`/api/v1/benchmark/${dataSource}/${symbol}`); } @@ -289,7 +289,7 @@ export class DataService { public fetchAsset({ dataSource, symbol - }: UniqueAsset): Observable { + }: AssetProfileIdentifier): Observable { return this.http.get(`/api/v1/asset/${dataSource}/${symbol}`).pipe( map((data) => { for (const item of data.marketData) { @@ -308,7 +308,7 @@ export class DataService { }: { range: DateRange; startDate: Date; - } & UniqueAsset): Observable { + } & AssetProfileIdentifier): Observable { let params = new HttpParams(); if (range) { @@ -630,7 +630,7 @@ export class DataService { ); } - public postBenchmark(benchmark: UniqueAsset) { + public postBenchmark(benchmark: AssetProfileIdentifier) { return this.http.post(`/api/v1/benchmark`, benchmark); } @@ -654,7 +654,7 @@ export class DataService { dataSource, symbol, tags - }: { tags: Tag[] } & UniqueAsset) { + }: { tags: Tag[] } & AssetProfileIdentifier) { return this.http.put( `/api/v1/portfolio/position/${dataSource}/${symbol}/tags`, { tags } diff --git a/libs/common/src/lib/helper.ts b/libs/common/src/lib/helper.ts index 7b25b62bc..eb0fce202 100644 --- a/libs/common/src/lib/helper.ts +++ b/libs/common/src/lib/helper.ts @@ -19,7 +19,7 @@ import { ghostfolioScraperApiSymbolPrefix, locale } from './config'; -import { Benchmark, UniqueAsset } from './interfaces'; +import { AssetProfileIdentifier, Benchmark } from './interfaces'; import { BenchmarkTrend, ColorScheme } from './types'; export const DATE_FORMAT = 'yyyy-MM-dd'; @@ -147,7 +147,10 @@ export function getAllActivityTypes(): ActivityType[] { return Object.values(ActivityType); } -export function getAssetProfileIdentifier({ dataSource, symbol }: UniqueAsset) { +export function getAssetProfileIdentifier({ + dataSource, + symbol +}: AssetProfileIdentifier) { return `${dataSource}-${symbol}`; } @@ -377,7 +380,7 @@ export function parseDate(date: string): Date | null { return parseISO(date); } -export function parseSymbol({ dataSource, symbol }: UniqueAsset) { +export function parseSymbol({ dataSource, symbol }: AssetProfileIdentifier) { const [ticker, exchange] = symbol.split('.'); return { diff --git a/libs/common/src/lib/interfaces/admin-data.interface.ts b/libs/common/src/lib/interfaces/admin-data.interface.ts index 2c6a5c501..6b139026b 100644 --- a/libs/common/src/lib/interfaces/admin-data.interface.ts +++ b/libs/common/src/lib/interfaces/admin-data.interface.ts @@ -1,13 +1,13 @@ -import { Role } from '@prisma/client'; +import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces'; -import { UniqueAsset } from './unique-asset.interface'; +import { Role } from '@prisma/client'; export interface AdminData { exchangeRates: ({ label1: string; label2: string; value: number; - } & UniqueAsset)[]; + } & AssetProfileIdentifier)[]; settings: { [key: string]: boolean | object | string | string[] }; transactionCount: number; userCount: number; diff --git a/libs/common/src/lib/interfaces/unique-asset.interface.ts b/libs/common/src/lib/interfaces/asset-profile-identifier.interface.ts similarity index 68% rename from libs/common/src/lib/interfaces/unique-asset.interface.ts rename to libs/common/src/lib/interfaces/asset-profile-identifier.interface.ts index 745a0d9a7..48fc18c2f 100644 --- a/libs/common/src/lib/interfaces/unique-asset.interface.ts +++ b/libs/common/src/lib/interfaces/asset-profile-identifier.interface.ts @@ -1,6 +1,6 @@ import { DataSource } from '@prisma/client'; -export interface UniqueAsset { +export interface AssetProfileIdentifier { dataSource: DataSource; symbol: string; } diff --git a/libs/common/src/lib/interfaces/index.ts b/libs/common/src/lib/interfaces/index.ts index bf3f6fd19..f7224407b 100644 --- a/libs/common/src/lib/interfaces/index.ts +++ b/libs/common/src/lib/interfaces/index.ts @@ -7,6 +7,7 @@ import type { AdminMarketData, AdminMarketDataItem } from './admin-market-data.interface'; +import type { AssetProfileIdentifier } from './asset-profile-identifier.interface'; import type { BenchmarkMarketDataDetails } from './benchmark-market-data-details.interface'; import type { BenchmarkProperty } from './benchmark-property.interface'; import type { Benchmark } from './benchmark.interface'; @@ -48,7 +49,6 @@ import type { Subscription } from './subscription.interface'; import type { SymbolMetrics } from './symbol-metrics.interface'; import type { SystemMessage } from './system-message.interface'; import type { TabConfiguration } from './tab-configuration.interface'; -import type { UniqueAsset } from './unique-asset.interface'; import type { UserSettings } from './user-settings.interface'; import type { User } from './user.interface'; @@ -61,6 +61,7 @@ export { AdminMarketData, AdminMarketDataDetails, AdminMarketDataItem, + AssetProfileIdentifier, Benchmark, BenchmarkMarketDataDetails, BenchmarkProperty, @@ -101,7 +102,6 @@ export { Subscription, SymbolMetrics, TabConfiguration, - UniqueAsset, User, UserSettings }; diff --git a/libs/common/src/lib/interfaces/responses/errors.interface.ts b/libs/common/src/lib/interfaces/responses/errors.interface.ts index 0b43592be..b830a9e30 100644 --- a/libs/common/src/lib/interfaces/responses/errors.interface.ts +++ b/libs/common/src/lib/interfaces/responses/errors.interface.ts @@ -1,6 +1,6 @@ -import { UniqueAsset } from '../unique-asset.interface'; +import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces'; export interface ResponseError { - errors?: UniqueAsset[]; + errors?: AssetProfileIdentifier[]; hasErrors: boolean; } diff --git a/libs/common/src/lib/models/portfolio-snapshot.ts b/libs/common/src/lib/models/portfolio-snapshot.ts index 909f44f2a..12235015a 100644 --- a/libs/common/src/lib/models/portfolio-snapshot.ts +++ b/libs/common/src/lib/models/portfolio-snapshot.ts @@ -1,5 +1,5 @@ import { transformToBig } from '@ghostfolio/common/class-transformer'; -import { UniqueAsset } from '@ghostfolio/common/interfaces'; +import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces'; import { TimelinePosition } from '@ghostfolio/common/models'; import { Big } from 'big.js'; @@ -9,7 +9,7 @@ export class PortfolioSnapshot { @Transform(transformToBig, { toClassOnly: true }) @Type(() => Big) currentValueInBaseCurrency: Big; - errors?: UniqueAsset[]; + errors?: AssetProfileIdentifier[]; @Transform(transformToBig, { toClassOnly: true }) @Type(() => Big) diff --git a/libs/ui/src/lib/activities-table/activities-table.component.ts b/libs/ui/src/lib/activities-table/activities-table.component.ts index 204cba0c2..a3caf5e1d 100644 --- a/libs/ui/src/lib/activities-table/activities-table.component.ts +++ b/libs/ui/src/lib/activities-table/activities-table.component.ts @@ -3,7 +3,7 @@ import { GfAssetProfileIconComponent } from '@ghostfolio/client/components/asset import { GfSymbolModule } from '@ghostfolio/client/pipes/symbol/symbol.module'; import { DEFAULT_PAGE_SIZE } from '@ghostfolio/common/config'; import { getDateFormatString, getLocale } from '@ghostfolio/common/helper'; -import { UniqueAsset } from '@ghostfolio/common/interfaces'; +import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces'; import { OrderWithAccount } from '@ghostfolio/common/types'; import { GfActivityTypeComponent } from '@ghostfolio/ui/activity-type'; import { GfNoTransactionsInfoComponent } from '@ghostfolio/ui/no-transactions-info'; @@ -99,7 +99,7 @@ export class GfActivitiesTableComponent @Output() export = new EventEmitter(); @Output() exportDrafts = new EventEmitter(); @Output() import = new EventEmitter(); - @Output() importDividends = new EventEmitter(); + @Output() importDividends = new EventEmitter(); @Output() pageChanged = new EventEmitter(); @Output() selectedActivities = new EventEmitter(); @Output() sortChanged = new EventEmitter(); @@ -263,7 +263,7 @@ export class GfActivitiesTableComponent alert(aComment); } - public onOpenPositionDialog({ dataSource, symbol }: UniqueAsset) { + public onOpenPositionDialog({ dataSource, symbol }: AssetProfileIdentifier) { this.router.navigate([], { queryParams: { dataSource, symbol, holdingDetailDialog: true } }); diff --git a/libs/ui/src/lib/assistant/interfaces/interfaces.ts b/libs/ui/src/lib/assistant/interfaces/interfaces.ts index 2597ccef0..3481b37cf 100644 --- a/libs/ui/src/lib/assistant/interfaces/interfaces.ts +++ b/libs/ui/src/lib/assistant/interfaces/interfaces.ts @@ -1,4 +1,4 @@ -import { UniqueAsset } from '@ghostfolio/common/interfaces'; +import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces'; import { DateRange } from '@ghostfolio/common/types'; export interface IDateRangeOption { @@ -6,7 +6,7 @@ export interface IDateRangeOption { value: DateRange; } -export interface ISearchResultItem extends UniqueAsset { +export interface ISearchResultItem extends AssetProfileIdentifier { assetSubClassString: string; currency: string; name: string; diff --git a/libs/ui/src/lib/benchmark/benchmark.component.ts b/libs/ui/src/lib/benchmark/benchmark.component.ts index 4dd4aa079..764d65c2c 100644 --- a/libs/ui/src/lib/benchmark/benchmark.component.ts +++ b/libs/ui/src/lib/benchmark/benchmark.component.ts @@ -1,5 +1,9 @@ import { getLocale, resolveMarketCondition } from '@ghostfolio/common/helper'; -import { Benchmark, UniqueAsset, User } from '@ghostfolio/common/interfaces'; +import { + AssetProfileIdentifier, + Benchmark, + User +} from '@ghostfolio/common/interfaces'; import { translate } from '@ghostfolio/ui/i18n'; import { GfTrendIndicatorComponent } from '@ghostfolio/ui/trend-indicator'; import { GfValueComponent } from '@ghostfolio/ui/value'; @@ -84,7 +88,7 @@ export class GfBenchmarkComponent implements OnChanges, OnDestroy { } } - public onOpenBenchmarkDialog({ dataSource, symbol }: UniqueAsset) { + public onOpenBenchmarkDialog({ dataSource, symbol }: AssetProfileIdentifier) { this.router.navigate([], { queryParams: { dataSource, symbol, benchmarkDetailDialog: true } }); @@ -95,7 +99,10 @@ export class GfBenchmarkComponent implements OnChanges, OnDestroy { this.unsubscribeSubject.complete(); } - private openBenchmarkDetailDialog({ dataSource, symbol }: UniqueAsset) { + private openBenchmarkDetailDialog({ + dataSource, + symbol + }: AssetProfileIdentifier) { const dialogRef = this.dialog.open(GfBenchmarkDetailDialogComponent, { data: { dataSource, diff --git a/libs/ui/src/lib/holdings-table/holdings-table.component.ts b/libs/ui/src/lib/holdings-table/holdings-table.component.ts index 12f071ef1..39a9baf5c 100644 --- a/libs/ui/src/lib/holdings-table/holdings-table.component.ts +++ b/libs/ui/src/lib/holdings-table/holdings-table.component.ts @@ -2,7 +2,10 @@ import { GfAssetProfileIconComponent } from '@ghostfolio/client/components/asset import { GfHoldingDetailDialogComponent } from '@ghostfolio/client/components/holding-detail-dialog/holding-detail-dialog.component'; import { GfSymbolModule } from '@ghostfolio/client/pipes/symbol/symbol.module'; import { getLocale } from '@ghostfolio/common/helper'; -import { PortfolioPosition, UniqueAsset } from '@ghostfolio/common/interfaces'; +import { + AssetProfileIdentifier, + PortfolioPosition +} from '@ghostfolio/common/interfaces'; import { GfNoTransactionsInfoComponent } from '@ghostfolio/ui/no-transactions-info'; import { GfValueComponent } from '@ghostfolio/ui/value'; @@ -102,7 +105,7 @@ export class GfHoldingsTableComponent implements OnChanges, OnDestroy, OnInit { } } - public onOpenHoldingDialog({ dataSource, symbol }: UniqueAsset) { + public onOpenHoldingDialog({ dataSource, symbol }: AssetProfileIdentifier) { if (this.hasPermissionToOpenDetails) { this.router.navigate([], { queryParams: { dataSource, symbol, holdingDetailDialog: true } diff --git a/libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts b/libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts index 8c9c29282..ad11c54f3 100644 --- a/libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts +++ b/libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts @@ -1,7 +1,10 @@ import { getTooltipOptions } from '@ghostfolio/common/chart-helper'; import { UNKNOWN_KEY } from '@ghostfolio/common/config'; import { getLocale, getTextColor } from '@ghostfolio/common/helper'; -import { PortfolioPosition, UniqueAsset } from '@ghostfolio/common/interfaces'; +import { + AssetProfileIdentifier, + PortfolioPosition +} from '@ghostfolio/common/interfaces'; import { ColorScheme } from '@ghostfolio/common/types'; import { translate } from '@ghostfolio/ui/i18n'; @@ -71,7 +74,7 @@ export class GfPortfolioProportionChartComponent }; } = {}; - @Output() proportionChartClicked = new EventEmitter(); + @Output() proportionChartClicked = new EventEmitter(); @ViewChild('chartCanvas') chartCanvas: ElementRef; diff --git a/libs/ui/src/lib/treemap-chart/treemap-chart.component.ts b/libs/ui/src/lib/treemap-chart/treemap-chart.component.ts index 3c1b3d540..f65894da3 100644 --- a/libs/ui/src/lib/treemap-chart/treemap-chart.component.ts +++ b/libs/ui/src/lib/treemap-chart/treemap-chart.component.ts @@ -1,5 +1,8 @@ import { getAnnualizedPerformancePercent } from '@ghostfolio/common/calculation-helper'; -import { PortfolioPosition, UniqueAsset } from '@ghostfolio/common/interfaces'; +import { + AssetProfileIdentifier, + PortfolioPosition +} from '@ghostfolio/common/interfaces'; import { CommonModule } from '@angular/common'; import { @@ -40,7 +43,7 @@ export class GfTreemapChartComponent @Input() cursor: string; @Input() holdings: PortfolioPosition[]; - @Output() treemapChartClicked = new EventEmitter(); + @Output() treemapChartClicked = new EventEmitter(); @ViewChild('chartCanvas') chartCanvas: ElementRef;