diff --git a/apps/api/src/app/portfolio/portfolio.controller.ts b/apps/api/src/app/portfolio/portfolio.controller.ts index 514858031..be0f42394 100644 --- a/apps/api/src/app/portfolio/portfolio.controller.ts +++ b/apps/api/src/app/portfolio/portfolio.controller.ts @@ -20,6 +20,7 @@ import { import { PortfolioDetails, PortfolioDividends, + PortfolioHoldingsResponse, PortfolioInvestments, PortfolioPerformanceResponse, PortfolioPublicDetails, @@ -280,6 +281,34 @@ export class PortfolioController { return { dividends }; } + @Get('holdings') + @UseGuards(AuthGuard('jwt'), HasPermissionGuard) + @UseInterceptors(RedactValuesInResponseInterceptor) + @UseInterceptors(TransformDataSourceInResponseInterceptor) + public async getHoldings( + @Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string, + @Query('accounts') filterByAccounts?: string, + @Query('assetClasses') filterByAssetClasses?: string, + @Query('query') filterBySearchQuery?: string, + @Query('tags') filterByTags?: string + ): Promise { + const filters = this.apiService.buildFiltersFromQueryParams({ + filterByAccounts, + filterByAssetClasses, + filterBySearchQuery, + filterByTags + }); + + const { holdings } = await this.portfolioService.getDetails({ + filters, + impersonationId, + dateRange: 'max', + userId: this.request.user.id + }); + + return { holdings: Object.values(holdings) }; + } + @Get('investments') @UseGuards(AuthGuard('jwt'), HasPermissionGuard) public async getInvestments( diff --git a/apps/client/src/app/pages/portfolio/holdings/holdings-page.component.ts b/apps/client/src/app/pages/portfolio/holdings/holdings-page.component.ts index 6c4a058b7..352cdaebf 100644 --- a/apps/client/src/app/pages/portfolio/holdings/holdings-page.component.ts +++ b/apps/client/src/app/pages/portfolio/holdings/holdings-page.component.ts @@ -3,11 +3,7 @@ import { PositionDetailDialog } from '@ghostfolio/client/components/position/pos 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 { - PortfolioDetails, - PortfolioPosition, - User -} from '@ghostfolio/common/interfaces'; +import { PortfolioPosition, User } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; @@ -28,8 +24,6 @@ export class HoldingsPageComponent implements OnDestroy, OnInit { public hasImpersonationId: boolean; public hasPermissionToCreateOrder: boolean; public holdings: PortfolioPosition[]; - public isLoading = false; - public portfolioDetails: PortfolioDetails; public user: User; private unsubscribeSubject = new Subject(); @@ -83,12 +77,10 @@ export class HoldingsPageComponent implements OnDestroy, OnInit { this.holdings = undefined; - this.fetchPortfolioDetails() + this.fetchHoldings() .pipe(takeUntil(this.unsubscribeSubject)) - .subscribe((portfolioDetails) => { - this.portfolioDetails = portfolioDetails; - - this.initialize(); + .subscribe(({ holdings }) => { + this.holdings = holdings; this.changeDetectorRef.markForCheck(); }); @@ -103,22 +95,12 @@ export class HoldingsPageComponent implements OnDestroy, OnInit { this.unsubscribeSubject.complete(); } - private fetchPortfolioDetails() { - return this.dataService.fetchPortfolioDetails({ + private fetchHoldings() { + return this.dataService.fetchHoldings({ filters: this.userService.getFilters() }); } - private initialize() { - this.holdings = []; - - for (const [symbol, holding] of Object.entries( - this.portfolioDetails.holdings - )) { - this.holdings.push(holding); - } - } - private openPositionDialog({ dataSource, symbol diff --git a/apps/client/src/app/services/data.service.ts b/apps/client/src/app/services/data.service.ts index 6555a964d..82f0aacf5 100644 --- a/apps/client/src/app/services/data.service.ts +++ b/apps/client/src/app/services/data.service.ts @@ -27,6 +27,7 @@ import { OAuthResponse, PortfolioDetails, PortfolioDividends, + PortfolioHoldingsResponse, PortfolioInvestments, PortfolioPerformanceResponse, PortfolioPublicDetails, @@ -229,6 +230,19 @@ export class DataService { ); } + public fetchHoldings({ + filters + }: { + filters?: Filter[]; + } = {}) { + return this.http.get( + '/api/v1/portfolio/holdings', + { + params: this.buildFiltersAsQueryParams({ filters }) + } + ); + } + public deleteAccess(aId: string) { return this.http.delete(`/api/v1/access/${aId}`); } diff --git a/libs/common/src/lib/interfaces/index.ts b/libs/common/src/lib/interfaces/index.ts index 7d77826d0..dba1ac79a 100644 --- a/libs/common/src/lib/interfaces/index.ts +++ b/libs/common/src/lib/interfaces/index.ts @@ -40,6 +40,7 @@ import type { BenchmarkResponse } from './responses/benchmark-response.interface import type { ResponseError } from './responses/errors.interface'; import type { ImportResponse } from './responses/import-response.interface'; import type { OAuthResponse } from './responses/oauth-response.interface'; +import type { PortfolioHoldingsResponse } from './responses/portfolio-holdings-response.interface'; import type { PortfolioPerformanceResponse } from './responses/portfolio-performance-response.interface'; import type { ScraperConfiguration } from './scraper-configuration.interface'; import type { Statistics } from './statistics.interface'; @@ -81,6 +82,7 @@ export { PortfolioChart, PortfolioDetails, PortfolioDividends, + PortfolioHoldingsResponse, PortfolioInvestments, PortfolioItem, PortfolioOverview, diff --git a/libs/common/src/lib/interfaces/responses/portfolio-holdings-response.interface.ts b/libs/common/src/lib/interfaces/responses/portfolio-holdings-response.interface.ts new file mode 100644 index 000000000..d2cf38f55 --- /dev/null +++ b/libs/common/src/lib/interfaces/responses/portfolio-holdings-response.interface.ts @@ -0,0 +1,5 @@ +import { PortfolioPosition } from '@ghostfolio/common/interfaces'; + +export interface PortfolioHoldingsResponse { + holdings: PortfolioPosition[]; +}