Browse Source

Add holdings endpoint

pull/3123/head
Thomas Kaul 1 year ago
parent
commit
ca3ffc934f
  1. 29
      apps/api/src/app/portfolio/portfolio.controller.ts
  2. 30
      apps/client/src/app/pages/portfolio/holdings/holdings-page.component.ts
  3. 14
      apps/client/src/app/services/data.service.ts
  4. 2
      libs/common/src/lib/interfaces/index.ts
  5. 5
      libs/common/src/lib/interfaces/responses/portfolio-holdings-response.interface.ts

29
apps/api/src/app/portfolio/portfolio.controller.ts

@ -20,6 +20,7 @@ import {
import { import {
PortfolioDetails, PortfolioDetails,
PortfolioDividends, PortfolioDividends,
PortfolioHoldingsResponse,
PortfolioInvestments, PortfolioInvestments,
PortfolioPerformanceResponse, PortfolioPerformanceResponse,
PortfolioPublicDetails, PortfolioPublicDetails,
@ -280,6 +281,34 @@ export class PortfolioController {
return { dividends }; 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<PortfolioHoldingsResponse> {
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') @Get('investments')
@UseGuards(AuthGuard('jwt'), HasPermissionGuard) @UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async getInvestments( public async getInvestments(

30
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 { DataService } from '@ghostfolio/client/services/data.service';
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service'; import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
import { UserService } from '@ghostfolio/client/services/user/user.service'; import { UserService } from '@ghostfolio/client/services/user/user.service';
import { import { PortfolioPosition, User } from '@ghostfolio/common/interfaces';
PortfolioDetails,
PortfolioPosition,
User
} from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
@ -28,8 +24,6 @@ export class HoldingsPageComponent implements OnDestroy, OnInit {
public hasImpersonationId: boolean; public hasImpersonationId: boolean;
public hasPermissionToCreateOrder: boolean; public hasPermissionToCreateOrder: boolean;
public holdings: PortfolioPosition[]; public holdings: PortfolioPosition[];
public isLoading = false;
public portfolioDetails: PortfolioDetails;
public user: User; public user: User;
private unsubscribeSubject = new Subject<void>(); private unsubscribeSubject = new Subject<void>();
@ -83,12 +77,10 @@ export class HoldingsPageComponent implements OnDestroy, OnInit {
this.holdings = undefined; this.holdings = undefined;
this.fetchPortfolioDetails() this.fetchHoldings()
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntil(this.unsubscribeSubject))
.subscribe((portfolioDetails) => { .subscribe(({ holdings }) => {
this.portfolioDetails = portfolioDetails; this.holdings = holdings;
this.initialize();
this.changeDetectorRef.markForCheck(); this.changeDetectorRef.markForCheck();
}); });
@ -103,22 +95,12 @@ export class HoldingsPageComponent implements OnDestroy, OnInit {
this.unsubscribeSubject.complete(); this.unsubscribeSubject.complete();
} }
private fetchPortfolioDetails() { private fetchHoldings() {
return this.dataService.fetchPortfolioDetails({ return this.dataService.fetchHoldings({
filters: this.userService.getFilters() filters: this.userService.getFilters()
}); });
} }
private initialize() {
this.holdings = [];
for (const [symbol, holding] of Object.entries(
this.portfolioDetails.holdings
)) {
this.holdings.push(holding);
}
}
private openPositionDialog({ private openPositionDialog({
dataSource, dataSource,
symbol symbol

14
apps/client/src/app/services/data.service.ts

@ -27,6 +27,7 @@ import {
OAuthResponse, OAuthResponse,
PortfolioDetails, PortfolioDetails,
PortfolioDividends, PortfolioDividends,
PortfolioHoldingsResponse,
PortfolioInvestments, PortfolioInvestments,
PortfolioPerformanceResponse, PortfolioPerformanceResponse,
PortfolioPublicDetails, PortfolioPublicDetails,
@ -229,6 +230,19 @@ export class DataService {
); );
} }
public fetchHoldings({
filters
}: {
filters?: Filter[];
} = {}) {
return this.http.get<PortfolioHoldingsResponse>(
'/api/v1/portfolio/holdings',
{
params: this.buildFiltersAsQueryParams({ filters })
}
);
}
public deleteAccess(aId: string) { public deleteAccess(aId: string) {
return this.http.delete<any>(`/api/v1/access/${aId}`); return this.http.delete<any>(`/api/v1/access/${aId}`);
} }

2
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 { ResponseError } from './responses/errors.interface';
import type { ImportResponse } from './responses/import-response.interface'; import type { ImportResponse } from './responses/import-response.interface';
import type { OAuthResponse } from './responses/oauth-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 { PortfolioPerformanceResponse } from './responses/portfolio-performance-response.interface';
import type { ScraperConfiguration } from './scraper-configuration.interface'; import type { ScraperConfiguration } from './scraper-configuration.interface';
import type { Statistics } from './statistics.interface'; import type { Statistics } from './statistics.interface';
@ -81,6 +82,7 @@ export {
PortfolioChart, PortfolioChart,
PortfolioDetails, PortfolioDetails,
PortfolioDividends, PortfolioDividends,
PortfolioHoldingsResponse,
PortfolioInvestments, PortfolioInvestments,
PortfolioItem, PortfolioItem,
PortfolioOverview, PortfolioOverview,

5
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[];
}
Loading…
Cancel
Save