|
|
@ -15,7 +15,6 @@ import { ConfigurationService } from '@ghostfolio/api/services/configuration/con |
|
|
import { ImpersonationService } from '@ghostfolio/api/services/impersonation/impersonation.service'; |
|
|
import { ImpersonationService } from '@ghostfolio/api/services/impersonation/impersonation.service'; |
|
|
import { getIntervalFromDateRange } from '@ghostfolio/common/calculation-helper'; |
|
|
import { getIntervalFromDateRange } from '@ghostfolio/common/calculation-helper'; |
|
|
import { |
|
|
import { |
|
|
DEFAULT_DATE_RANGE, |
|
|
|
|
|
HEADER_KEY_IMPERSONATION, |
|
|
HEADER_KEY_IMPERSONATION, |
|
|
UNKNOWN_KEY |
|
|
UNKNOWN_KEY |
|
|
} from '@ghostfolio/common/config'; |
|
|
} from '@ghostfolio/common/config'; |
|
|
@ -34,11 +33,7 @@ import { |
|
|
isRestrictedView, |
|
|
isRestrictedView, |
|
|
permissions |
|
|
permissions |
|
|
} from '@ghostfolio/common/permissions'; |
|
|
} from '@ghostfolio/common/permissions'; |
|
|
import type { |
|
|
import type { RequestWithUser } from '@ghostfolio/common/types'; |
|
|
DateRange, |
|
|
|
|
|
GroupBy, |
|
|
|
|
|
RequestWithUser |
|
|
|
|
|
} from '@ghostfolio/common/types'; |
|
|
|
|
|
|
|
|
|
|
|
import { |
|
|
import { |
|
|
Body, |
|
|
Body, |
|
|
@ -60,6 +55,11 @@ import { AssetClass, AssetSubClass, DataSource } from '@prisma/client'; |
|
|
import { Big } from 'big.js'; |
|
|
import { Big } from 'big.js'; |
|
|
import { StatusCodes, getReasonPhrase } from 'http-status-codes'; |
|
|
import { StatusCodes, getReasonPhrase } from 'http-status-codes'; |
|
|
|
|
|
|
|
|
|
|
|
import { GetDetailsDto } from './get-details.dto'; |
|
|
|
|
|
import { GetDividendsDto } from './get-dividends.dto'; |
|
|
|
|
|
import { GetHoldingsDto } from './get-holdings.dto'; |
|
|
|
|
|
import { GetInvestmentsDto } from './get-investments.dto'; |
|
|
|
|
|
import { GetPerformanceDto } from './get-performance.dto'; |
|
|
import { PortfolioService } from './portfolio.service'; |
|
|
import { PortfolioService } from './portfolio.service'; |
|
|
import { UpdateHoldingTagsDto } from './update-holding-tags.dto'; |
|
|
import { UpdateHoldingTagsDto } from './update-holding-tags.dto'; |
|
|
|
|
|
|
|
|
@ -82,16 +82,17 @@ export class PortfolioController { |
|
|
@UseInterceptors(TransformDataSourceInResponseInterceptor) |
|
|
@UseInterceptors(TransformDataSourceInResponseInterceptor) |
|
|
public async getDetails( |
|
|
public async getDetails( |
|
|
@Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string, |
|
|
@Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string, |
|
|
@Query('accounts') filterByAccounts?: string, |
|
|
@Query() |
|
|
@Query('assetClasses') filterByAssetClasses?: string, |
|
|
{ |
|
|
@Query('dataSource') filterByDataSource?: string, |
|
|
accounts: filterByAccounts, |
|
|
@Query('range') dateRange: DateRange = DEFAULT_DATE_RANGE, |
|
|
assetClasses: filterByAssetClasses, |
|
|
@Query('symbol') filterBySymbol?: string, |
|
|
dataSource: filterByDataSource, |
|
|
@Query('tags') filterByTags?: string, |
|
|
range, |
|
|
@Query('withMarkets') withMarketsParam = 'false' |
|
|
symbol: filterBySymbol, |
|
|
|
|
|
tags: filterByTags, |
|
|
|
|
|
withMarkets |
|
|
|
|
|
}: GetDetailsDto |
|
|
): Promise<PortfolioDetails & { hasError: boolean }> { |
|
|
): Promise<PortfolioDetails & { hasError: boolean }> { |
|
|
const withMarkets = withMarketsParam === 'true'; |
|
|
|
|
|
|
|
|
|
|
|
let hasDetails = true; |
|
|
let hasDetails = true; |
|
|
let hasError = false; |
|
|
let hasError = false; |
|
|
|
|
|
|
|
|
@ -118,10 +119,10 @@ export class PortfolioController { |
|
|
platforms, |
|
|
platforms, |
|
|
summary |
|
|
summary |
|
|
} = await this.portfolioService.getDetails({ |
|
|
} = await this.portfolioService.getDetails({ |
|
|
dateRange, |
|
|
|
|
|
filters, |
|
|
filters, |
|
|
impersonationId, |
|
|
impersonationId, |
|
|
withMarkets, |
|
|
withMarkets, |
|
|
|
|
|
dateRange: range, |
|
|
userId: this.request.user.id, |
|
|
userId: this.request.user.id, |
|
|
withSummary: true |
|
|
withSummary: true |
|
|
}); |
|
|
}); |
|
|
@ -326,20 +327,23 @@ export class PortfolioController { |
|
|
@UseInterceptors(TransformDataSourceInRequestInterceptor) |
|
|
@UseInterceptors(TransformDataSourceInRequestInterceptor) |
|
|
public async getDividends( |
|
|
public async getDividends( |
|
|
@Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string, |
|
|
@Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string, |
|
|
@Query('accounts') filterByAccounts?: string, |
|
|
@Query() |
|
|
@Query('assetClasses') filterByAssetClasses?: string, |
|
|
{ |
|
|
@Query('dataSource') filterByDataSource?: string, |
|
|
accounts, |
|
|
@Query('groupBy') groupBy?: GroupBy, |
|
|
assetClasses, |
|
|
@Query('range') dateRange: DateRange = DEFAULT_DATE_RANGE, |
|
|
dataSource, |
|
|
@Query('symbol') filterBySymbol?: string, |
|
|
groupBy, |
|
|
@Query('tags') filterByTags?: string |
|
|
range, |
|
|
|
|
|
symbol, |
|
|
|
|
|
tags |
|
|
|
|
|
}: GetDividendsDto |
|
|
): Promise<PortfolioDividendsResponse> { |
|
|
): Promise<PortfolioDividendsResponse> { |
|
|
const filters = this.apiService.buildFiltersFromQueryParams({ |
|
|
const filters = this.apiService.buildFiltersFromQueryParams({ |
|
|
filterByAccounts, |
|
|
filterByAccounts: accounts, |
|
|
filterByAssetClasses, |
|
|
filterByAssetClasses: assetClasses, |
|
|
filterByDataSource, |
|
|
filterByDataSource: dataSource, |
|
|
filterBySymbol, |
|
|
filterBySymbol: symbol, |
|
|
filterByTags |
|
|
filterByTags: tags |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
const impersonationUserId = |
|
|
const impersonationUserId = |
|
|
@ -349,7 +353,9 @@ export class PortfolioController { |
|
|
const { settings } = await this.userService.user({ id: userId }); |
|
|
const { settings } = await this.userService.user({ id: userId }); |
|
|
const userCurrency = settings.settings.baseCurrency; |
|
|
const userCurrency = settings.settings.baseCurrency; |
|
|
|
|
|
|
|
|
const { endDate, startDate } = getIntervalFromDateRange({ dateRange }); |
|
|
const { endDate, startDate } = getIntervalFromDateRange({ |
|
|
|
|
|
dateRange: range |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
const { activities } = await this.activitiesService.getActivities({ |
|
|
const { activities } = await this.activitiesService.getActivities({ |
|
|
endDate, |
|
|
endDate, |
|
|
@ -429,29 +435,32 @@ export class PortfolioController { |
|
|
@UseInterceptors(TransformDataSourceInResponseInterceptor) |
|
|
@UseInterceptors(TransformDataSourceInResponseInterceptor) |
|
|
public async getHoldings( |
|
|
public async getHoldings( |
|
|
@Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string, |
|
|
@Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string, |
|
|
@Query('accounts') filterByAccounts?: string, |
|
|
@Query() |
|
|
@Query('assetClasses') filterByAssetClasses?: string, |
|
|
{ |
|
|
@Query('dataSource') filterByDataSource?: string, |
|
|
accounts, |
|
|
@Query('holdingType') filterByHoldingType?: string, |
|
|
assetClasses, |
|
|
@Query('query') filterBySearchQuery?: string, |
|
|
dataSource, |
|
|
@Query('range') dateRange: DateRange = DEFAULT_DATE_RANGE, |
|
|
holdingType, |
|
|
@Query('symbol') filterBySymbol?: string, |
|
|
query, |
|
|
@Query('tags') filterByTags?: string |
|
|
range, |
|
|
|
|
|
symbol, |
|
|
|
|
|
tags |
|
|
|
|
|
}: GetHoldingsDto |
|
|
): Promise<PortfolioHoldingsResponse> { |
|
|
): Promise<PortfolioHoldingsResponse> { |
|
|
const filters = this.apiService.buildFiltersFromQueryParams({ |
|
|
const filters = this.apiService.buildFiltersFromQueryParams({ |
|
|
filterByAccounts, |
|
|
filterByAccounts: accounts, |
|
|
filterByAssetClasses, |
|
|
filterByAssetClasses: assetClasses, |
|
|
filterByDataSource, |
|
|
filterByDataSource: dataSource, |
|
|
filterByHoldingType, |
|
|
filterByHoldingType: holdingType, |
|
|
filterBySearchQuery, |
|
|
filterBySearchQuery: query, |
|
|
filterBySymbol, |
|
|
filterBySymbol: symbol, |
|
|
filterByTags |
|
|
filterByTags: tags |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
const holdings = await this.portfolioService.getHoldings({ |
|
|
const holdings = await this.portfolioService.getHoldings({ |
|
|
dateRange, |
|
|
|
|
|
filters, |
|
|
filters, |
|
|
impersonationId, |
|
|
impersonationId, |
|
|
|
|
|
dateRange: range, |
|
|
userId: this.request.user.id |
|
|
userId: this.request.user.id |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
@ -463,27 +472,30 @@ export class PortfolioController { |
|
|
@UseInterceptors(TransformDataSourceInRequestInterceptor) |
|
|
@UseInterceptors(TransformDataSourceInRequestInterceptor) |
|
|
public async getInvestments( |
|
|
public async getInvestments( |
|
|
@Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string, |
|
|
@Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string, |
|
|
@Query('accounts') filterByAccounts?: string, |
|
|
@Query() |
|
|
@Query('assetClasses') filterByAssetClasses?: string, |
|
|
{ |
|
|
@Query('dataSource') filterByDataSource?: string, |
|
|
accounts, |
|
|
@Query('groupBy') groupBy?: GroupBy, |
|
|
assetClasses, |
|
|
@Query('range') dateRange: DateRange = DEFAULT_DATE_RANGE, |
|
|
dataSource, |
|
|
@Query('symbol') filterBySymbol?: string, |
|
|
groupBy, |
|
|
@Query('tags') filterByTags?: string |
|
|
range, |
|
|
|
|
|
symbol, |
|
|
|
|
|
tags |
|
|
|
|
|
}: GetInvestmentsDto |
|
|
): Promise<PortfolioInvestmentsResponse> { |
|
|
): Promise<PortfolioInvestmentsResponse> { |
|
|
const filters = this.apiService.buildFiltersFromQueryParams({ |
|
|
const filters = this.apiService.buildFiltersFromQueryParams({ |
|
|
filterByAccounts, |
|
|
filterByAccounts: accounts, |
|
|
filterByAssetClasses, |
|
|
filterByAssetClasses: assetClasses, |
|
|
filterByDataSource, |
|
|
filterByDataSource: dataSource, |
|
|
filterBySymbol, |
|
|
filterBySymbol: symbol, |
|
|
filterByTags |
|
|
filterByTags: tags |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
let { investments, streaks } = await this.portfolioService.getInvestments({ |
|
|
let { investments, streaks } = await this.portfolioService.getInvestments({ |
|
|
dateRange, |
|
|
|
|
|
filters, |
|
|
filters, |
|
|
groupBy, |
|
|
groupBy, |
|
|
impersonationId, |
|
|
impersonationId, |
|
|
|
|
|
dateRange: range, |
|
|
savingsRate: this.request.user?.settings?.settings.savingsRate, |
|
|
savingsRate: this.request.user?.settings?.settings.savingsRate, |
|
|
userId: this.request.user.id |
|
|
userId: this.request.user.id |
|
|
}); |
|
|
}); |
|
|
@ -536,29 +548,30 @@ export class PortfolioController { |
|
|
@Version('2') |
|
|
@Version('2') |
|
|
public async getPerformanceV2( |
|
|
public async getPerformanceV2( |
|
|
@Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string, |
|
|
@Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string, |
|
|
@Query('accounts') filterByAccounts?: string, |
|
|
@Query() |
|
|
@Query('assetClasses') filterByAssetClasses?: string, |
|
|
{ |
|
|
@Query('dataSource') filterByDataSource?: string, |
|
|
accounts, |
|
|
@Query('range') dateRange: DateRange = DEFAULT_DATE_RANGE, |
|
|
assetClasses, |
|
|
@Query('symbol') filterBySymbol?: string, |
|
|
dataSource, |
|
|
@Query('tags') filterByTags?: string, |
|
|
range, |
|
|
@Query('withExcludedAccounts') withExcludedAccountsParam = 'false' |
|
|
symbol, |
|
|
|
|
|
tags, |
|
|
|
|
|
withExcludedAccounts |
|
|
|
|
|
}: GetPerformanceDto |
|
|
): Promise<PortfolioPerformanceResponse> { |
|
|
): Promise<PortfolioPerformanceResponse> { |
|
|
const withExcludedAccounts = withExcludedAccountsParam === 'true'; |
|
|
|
|
|
|
|
|
|
|
|
const filters = this.apiService.buildFiltersFromQueryParams({ |
|
|
const filters = this.apiService.buildFiltersFromQueryParams({ |
|
|
filterByAccounts, |
|
|
filterByAccounts: accounts, |
|
|
filterByAssetClasses, |
|
|
filterByAssetClasses: assetClasses, |
|
|
filterByDataSource, |
|
|
filterByDataSource: dataSource, |
|
|
filterBySymbol, |
|
|
filterBySymbol: symbol, |
|
|
filterByTags |
|
|
filterByTags: tags |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
const performanceInformation = await this.portfolioService.getPerformance({ |
|
|
const performanceInformation = await this.portfolioService.getPerformance({ |
|
|
dateRange, |
|
|
|
|
|
filters, |
|
|
filters, |
|
|
impersonationId, |
|
|
impersonationId, |
|
|
withExcludedAccounts, |
|
|
withExcludedAccounts, |
|
|
|
|
|
dateRange: range, |
|
|
userId: this.request.user.id |
|
|
userId: this.request.user.id |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
|