|
|
@ -1,7 +1,8 @@ |
|
|
import { ActivitiesService } from '@ghostfolio/api/app/activities/activities.service'; |
|
|
import { ActivitiesService } from '@ghostfolio/api/app/activities/activities.service'; |
|
|
import { UserService } from '@ghostfolio/api/app/user/user.service'; |
|
|
|
|
|
import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator'; |
|
|
import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator'; |
|
|
|
|
|
import { Impersonation } from '@ghostfolio/api/decorators/impersonation.decorator'; |
|
|
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard'; |
|
|
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard'; |
|
|
|
|
|
import { ImpersonationGuard } from '@ghostfolio/api/guards/impersonation.guard'; |
|
|
import { |
|
|
import { |
|
|
hasNotDefinedValuesInObject, |
|
|
hasNotDefinedValuesInObject, |
|
|
nullifyValuesInObject |
|
|
nullifyValuesInObject |
|
|
@ -12,12 +13,8 @@ import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interce |
|
|
import { TransformDataSourceInResponseInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-response/transform-data-source-in-response.interceptor'; |
|
|
import { TransformDataSourceInResponseInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-response/transform-data-source-in-response.interceptor'; |
|
|
import { ApiService } from '@ghostfolio/api/services/api/api.service'; |
|
|
import { ApiService } from '@ghostfolio/api/services/api/api.service'; |
|
|
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; |
|
|
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.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 { UNKNOWN_KEY } from '@ghostfolio/common/config'; |
|
|
HEADER_KEY_IMPERSONATION, |
|
|
|
|
|
UNKNOWN_KEY |
|
|
|
|
|
} from '@ghostfolio/common/config'; |
|
|
|
|
|
import { SubscriptionType } from '@ghostfolio/common/enums'; |
|
|
import { SubscriptionType } from '@ghostfolio/common/enums'; |
|
|
import { |
|
|
import { |
|
|
PortfolioDetails, |
|
|
PortfolioDetails, |
|
|
@ -33,13 +30,15 @@ import { |
|
|
isRestrictedView, |
|
|
isRestrictedView, |
|
|
permissions |
|
|
permissions |
|
|
} from '@ghostfolio/common/permissions'; |
|
|
} from '@ghostfolio/common/permissions'; |
|
|
import type { RequestWithUser } from '@ghostfolio/common/types'; |
|
|
import type { |
|
|
|
|
|
ImpersonationContext, |
|
|
|
|
|
RequestWithUser |
|
|
|
|
|
} from '@ghostfolio/common/types'; |
|
|
|
|
|
|
|
|
import { |
|
|
import { |
|
|
Body, |
|
|
Body, |
|
|
Controller, |
|
|
Controller, |
|
|
Get, |
|
|
Get, |
|
|
Headers, |
|
|
|
|
|
HttpException, |
|
|
HttpException, |
|
|
Inject, |
|
|
Inject, |
|
|
Param, |
|
|
Param, |
|
|
@ -69,19 +68,17 @@ export class PortfolioController { |
|
|
private readonly activitiesService: ActivitiesService, |
|
|
private readonly activitiesService: ActivitiesService, |
|
|
private readonly apiService: ApiService, |
|
|
private readonly apiService: ApiService, |
|
|
private readonly configurationService: ConfigurationService, |
|
|
private readonly configurationService: ConfigurationService, |
|
|
private readonly impersonationService: ImpersonationService, |
|
|
|
|
|
private readonly portfolioService: PortfolioService, |
|
|
private readonly portfolioService: PortfolioService, |
|
|
@Inject(REQUEST) private readonly request: RequestWithUser, |
|
|
@Inject(REQUEST) private readonly request: RequestWithUser |
|
|
private readonly userService: UserService |
|
|
|
|
|
) {} |
|
|
) {} |
|
|
|
|
|
|
|
|
@Get('details') |
|
|
@Get('details') |
|
|
@UseGuards(AuthGuard('jwt'), HasPermissionGuard) |
|
|
@UseGuards(AuthGuard('jwt'), HasPermissionGuard, ImpersonationGuard) |
|
|
@UseInterceptors(RedactValuesInResponseInterceptor) |
|
|
@UseInterceptors(RedactValuesInResponseInterceptor) |
|
|
@UseInterceptors(TransformDataSourceInRequestInterceptor) |
|
|
@UseInterceptors(TransformDataSourceInRequestInterceptor) |
|
|
@UseInterceptors(TransformDataSourceInResponseInterceptor) |
|
|
@UseInterceptors(TransformDataSourceInResponseInterceptor) |
|
|
public async getDetails( |
|
|
public async getDetails( |
|
|
@Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string, |
|
|
@Impersonation() { accessId, userId }: ImpersonationContext, |
|
|
@Query() |
|
|
@Query() |
|
|
{ |
|
|
{ |
|
|
accounts: filterByAccounts, |
|
|
accounts: filterByAccounts, |
|
|
@ -120,10 +117,9 @@ export class PortfolioController { |
|
|
summary |
|
|
summary |
|
|
} = await this.portfolioService.getDetails({ |
|
|
} = await this.portfolioService.getDetails({ |
|
|
filters, |
|
|
filters, |
|
|
impersonationId, |
|
|
userId, |
|
|
withMarkets, |
|
|
withMarkets, |
|
|
dateRange: range, |
|
|
dateRange: range, |
|
|
userId: this.request.user.id, |
|
|
|
|
|
withSummary: true |
|
|
withSummary: true |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
@ -135,8 +131,8 @@ export class PortfolioController { |
|
|
|
|
|
|
|
|
if ( |
|
|
if ( |
|
|
hasReadRestrictedAccessPermission({ |
|
|
hasReadRestrictedAccessPermission({ |
|
|
impersonationId, |
|
|
accesses: this.request.user?.accessesGet, |
|
|
accesses: this.request.user?.accessesGet |
|
|
impersonationId: accessId |
|
|
}) || |
|
|
}) || |
|
|
isRestrictedView(this.request.user) |
|
|
isRestrictedView(this.request.user) |
|
|
) { |
|
|
) { |
|
|
@ -179,8 +175,8 @@ export class PortfolioController { |
|
|
if ( |
|
|
if ( |
|
|
hasDetails === false || |
|
|
hasDetails === false || |
|
|
hasReadRestrictedAccessPermission({ |
|
|
hasReadRestrictedAccessPermission({ |
|
|
impersonationId, |
|
|
accesses: this.request.user?.accessesGet, |
|
|
accesses: this.request.user?.accessesGet |
|
|
impersonationId: accessId |
|
|
}) || |
|
|
}) || |
|
|
isRestrictedView(this.request.user) |
|
|
isRestrictedView(this.request.user) |
|
|
) { |
|
|
) { |
|
|
@ -323,10 +319,10 @@ export class PortfolioController { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@Get('dividends') |
|
|
@Get('dividends') |
|
|
@UseGuards(AuthGuard('jwt'), HasPermissionGuard) |
|
|
@UseGuards(AuthGuard('jwt'), HasPermissionGuard, ImpersonationGuard) |
|
|
@UseInterceptors(TransformDataSourceInRequestInterceptor) |
|
|
@UseInterceptors(TransformDataSourceInRequestInterceptor) |
|
|
public async getDividends( |
|
|
public async getDividends( |
|
|
@Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string, |
|
|
@Impersonation() { accessId, userId, userSettings }: ImpersonationContext, |
|
|
@Query() |
|
|
@Query() |
|
|
{ |
|
|
{ |
|
|
accounts, |
|
|
accounts, |
|
|
@ -346,12 +342,7 @@ export class PortfolioController { |
|
|
filterByTags: tags |
|
|
filterByTags: tags |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
const impersonationUserId = |
|
|
const userCurrency = userSettings.baseCurrency; |
|
|
await this.impersonationService.validateImpersonationId(impersonationId); |
|
|
|
|
|
const userId = impersonationUserId || this.request.user.id; |
|
|
|
|
|
|
|
|
|
|
|
const { settings } = await this.userService.user({ id: userId }); |
|
|
|
|
|
const userCurrency = settings.settings.baseCurrency; |
|
|
|
|
|
|
|
|
|
|
|
const { endDate, startDate } = getIntervalFromDateRange({ |
|
|
const { endDate, startDate } = getIntervalFromDateRange({ |
|
|
dateRange: range |
|
|
dateRange: range |
|
|
@ -374,8 +365,8 @@ export class PortfolioController { |
|
|
|
|
|
|
|
|
if ( |
|
|
if ( |
|
|
hasReadRestrictedAccessPermission({ |
|
|
hasReadRestrictedAccessPermission({ |
|
|
impersonationId, |
|
|
accesses: this.request.user?.accessesGet, |
|
|
accesses: this.request.user?.accessesGet |
|
|
impersonationId: accessId |
|
|
}) || |
|
|
}) || |
|
|
isRestrictedView(this.request.user) |
|
|
isRestrictedView(this.request.user) |
|
|
) { |
|
|
) { |
|
|
@ -406,17 +397,16 @@ export class PortfolioController { |
|
|
@UseInterceptors(RedactValuesInResponseInterceptor) |
|
|
@UseInterceptors(RedactValuesInResponseInterceptor) |
|
|
@UseInterceptors(TransformDataSourceInRequestInterceptor) |
|
|
@UseInterceptors(TransformDataSourceInRequestInterceptor) |
|
|
@UseInterceptors(TransformDataSourceInResponseInterceptor) |
|
|
@UseInterceptors(TransformDataSourceInResponseInterceptor) |
|
|
@UseGuards(AuthGuard('jwt'), HasPermissionGuard) |
|
|
@UseGuards(AuthGuard('jwt'), HasPermissionGuard, ImpersonationGuard) |
|
|
public async getHolding( |
|
|
public async getHolding( |
|
|
@Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string, |
|
|
@Impersonation() { userId }: ImpersonationContext, |
|
|
@Param('dataSource') dataSource: DataSource, |
|
|
@Param('dataSource') dataSource: DataSource, |
|
|
@Param('symbol') symbol: string |
|
|
@Param('symbol') symbol: string |
|
|
): Promise<PortfolioHoldingResponse> { |
|
|
): Promise<PortfolioHoldingResponse> { |
|
|
const holding = await this.portfolioService.getHolding({ |
|
|
const holding = await this.portfolioService.getHolding({ |
|
|
dataSource, |
|
|
dataSource, |
|
|
impersonationId, |
|
|
|
|
|
symbol, |
|
|
symbol, |
|
|
userId: this.request.user.id |
|
|
userId |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
if (!holding) { |
|
|
if (!holding) { |
|
|
@ -430,12 +420,12 @@ export class PortfolioController { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@Get('holdings') |
|
|
@Get('holdings') |
|
|
@UseGuards(AuthGuard('jwt'), HasPermissionGuard) |
|
|
@UseGuards(AuthGuard('jwt'), HasPermissionGuard, ImpersonationGuard) |
|
|
@UseInterceptors(RedactValuesInResponseInterceptor) |
|
|
@UseInterceptors(RedactValuesInResponseInterceptor) |
|
|
@UseInterceptors(TransformDataSourceInRequestInterceptor) |
|
|
@UseInterceptors(TransformDataSourceInRequestInterceptor) |
|
|
@UseInterceptors(TransformDataSourceInResponseInterceptor) |
|
|
@UseInterceptors(TransformDataSourceInResponseInterceptor) |
|
|
public async getHoldings( |
|
|
public async getHoldings( |
|
|
@Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string, |
|
|
@Impersonation() { userId }: ImpersonationContext, |
|
|
@Query() |
|
|
@Query() |
|
|
{ |
|
|
{ |
|
|
accounts, |
|
|
accounts, |
|
|
@ -460,19 +450,18 @@ export class PortfolioController { |
|
|
|
|
|
|
|
|
const holdings = await this.portfolioService.getHoldings({ |
|
|
const holdings = await this.portfolioService.getHoldings({ |
|
|
filters, |
|
|
filters, |
|
|
impersonationId, |
|
|
userId, |
|
|
dateRange: range, |
|
|
dateRange: range |
|
|
userId: this.request.user.id |
|
|
|
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
return { holdings }; |
|
|
return { holdings }; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@Get('investments') |
|
|
@Get('investments') |
|
|
@UseGuards(AuthGuard('jwt'), HasPermissionGuard) |
|
|
@UseGuards(AuthGuard('jwt'), HasPermissionGuard, ImpersonationGuard) |
|
|
@UseInterceptors(TransformDataSourceInRequestInterceptor) |
|
|
@UseInterceptors(TransformDataSourceInRequestInterceptor) |
|
|
public async getInvestments( |
|
|
public async getInvestments( |
|
|
@Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string, |
|
|
@Impersonation() { accessId, userId }: ImpersonationContext, |
|
|
@Query() |
|
|
@Query() |
|
|
{ |
|
|
{ |
|
|
accounts, |
|
|
accounts, |
|
|
@ -496,15 +485,14 @@ export class PortfolioController { |
|
|
await this.portfolioService.getInvestments({ |
|
|
await this.portfolioService.getInvestments({ |
|
|
filters, |
|
|
filters, |
|
|
groupBy, |
|
|
groupBy, |
|
|
impersonationId, |
|
|
userId, |
|
|
dateRange: range, |
|
|
dateRange: range |
|
|
userId: this.request.user.id |
|
|
|
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
if ( |
|
|
if ( |
|
|
hasReadRestrictedAccessPermission({ |
|
|
hasReadRestrictedAccessPermission({ |
|
|
impersonationId, |
|
|
accesses: this.request.user?.accessesGet, |
|
|
accesses: this.request.user?.accessesGet |
|
|
impersonationId: accessId |
|
|
}) || |
|
|
}) || |
|
|
isRestrictedView(this.request.user) |
|
|
isRestrictedView(this.request.user) |
|
|
) { |
|
|
) { |
|
|
@ -544,13 +532,13 @@ export class PortfolioController { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@Get('performance') |
|
|
@Get('performance') |
|
|
@UseGuards(AuthGuard('jwt'), HasPermissionGuard) |
|
|
@UseGuards(AuthGuard('jwt'), HasPermissionGuard, ImpersonationGuard) |
|
|
@UseInterceptors(PerformanceLoggingInterceptor) |
|
|
@UseInterceptors(PerformanceLoggingInterceptor) |
|
|
@UseInterceptors(TransformDataSourceInRequestInterceptor) |
|
|
@UseInterceptors(TransformDataSourceInRequestInterceptor) |
|
|
@UseInterceptors(TransformDataSourceInResponseInterceptor) |
|
|
@UseInterceptors(TransformDataSourceInResponseInterceptor) |
|
|
@Version('2') |
|
|
@Version('2') |
|
|
public async getPerformanceV2( |
|
|
public async getPerformanceV2( |
|
|
@Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string, |
|
|
@Impersonation() { accessId, userId }: ImpersonationContext, |
|
|
@Query() |
|
|
@Query() |
|
|
{ |
|
|
{ |
|
|
accounts, |
|
|
accounts, |
|
|
@ -572,16 +560,15 @@ export class PortfolioController { |
|
|
|
|
|
|
|
|
const performanceInformation = await this.portfolioService.getPerformance({ |
|
|
const performanceInformation = await this.portfolioService.getPerformance({ |
|
|
filters, |
|
|
filters, |
|
|
impersonationId, |
|
|
userId, |
|
|
withExcludedAccounts, |
|
|
withExcludedAccounts, |
|
|
dateRange: range, |
|
|
dateRange: range |
|
|
userId: this.request.user.id |
|
|
|
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
if ( |
|
|
if ( |
|
|
hasReadRestrictedAccessPermission({ |
|
|
hasReadRestrictedAccessPermission({ |
|
|
impersonationId, |
|
|
accesses: this.request.user?.accessesGet, |
|
|
accesses: this.request.user?.accessesGet |
|
|
impersonationId: accessId |
|
|
}) || |
|
|
}) || |
|
|
isRestrictedView(this.request.user) || |
|
|
isRestrictedView(this.request.user) || |
|
|
this.request.user.settings.settings.viewMode === 'ZEN' |
|
|
this.request.user.settings.settings.viewMode === 'ZEN' |
|
|
@ -658,18 +645,20 @@ export class PortfolioController { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@Get('report') |
|
|
@Get('report') |
|
|
@UseGuards(AuthGuard('jwt'), HasPermissionGuard) |
|
|
@UseGuards(AuthGuard('jwt'), HasPermissionGuard, ImpersonationGuard) |
|
|
public async getReport( |
|
|
public async getReport( |
|
|
@Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string |
|
|
@Impersonation() { accessId, userId }: ImpersonationContext |
|
|
): Promise<PortfolioReportResponse> { |
|
|
): Promise<PortfolioReportResponse> { |
|
|
const report = await this.portfolioService.getReport({ |
|
|
const report = await this.portfolioService.getReport({ userId }); |
|
|
impersonationId, |
|
|
|
|
|
userId: this.request.user.id |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
if ( |
|
|
if ( |
|
|
this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION') && |
|
|
hasReadRestrictedAccessPermission({ |
|
|
this.request.user.subscription?.type === SubscriptionType.Basic |
|
|
accesses: this.request.user?.accessesGet, |
|
|
|
|
|
impersonationId: accessId |
|
|
|
|
|
}) || |
|
|
|
|
|
isRestrictedView(this.request.user) || |
|
|
|
|
|
(this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION') && |
|
|
|
|
|
this.request.user.subscription?.type === SubscriptionType.Basic) |
|
|
) { |
|
|
) { |
|
|
for (const category of report.xRay.categories) { |
|
|
for (const category of report.xRay.categories) { |
|
|
category.rules = null; |
|
|
category.rules = null; |
|
|
@ -687,7 +676,7 @@ export class PortfolioController { |
|
|
@HasPermission(permissions.updateActivity) |
|
|
@HasPermission(permissions.updateActivity) |
|
|
@Put('holding/:dataSource/:symbol/tags') |
|
|
@Put('holding/:dataSource/:symbol/tags') |
|
|
@UseInterceptors(TransformDataSourceInRequestInterceptor) |
|
|
@UseInterceptors(TransformDataSourceInRequestInterceptor) |
|
|
@UseGuards(AuthGuard('jwt'), HasPermissionGuard) |
|
|
@UseGuards(AuthGuard('jwt'), HasPermissionGuard, ImpersonationGuard) |
|
|
public async updateHoldingTags( |
|
|
public async updateHoldingTags( |
|
|
@Body() data: UpdateHoldingTagsDto, |
|
|
@Body() data: UpdateHoldingTagsDto, |
|
|
@Param('dataSource') dataSource: DataSource, |
|
|
@Param('dataSource') dataSource: DataSource, |
|
|
|