Feature/refactor value redaction interceptor (#1624)
* Reuse redactAttributes() * Update changelog
This commit is contained in:
parent
bf9b60aa74
commit
52b3ad6dc3
@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
- Removed the toggle _Original Shares_ vs. _Current Shares_ on the allocations page
|
- Removed the toggle _Original Shares_ vs. _Current Shares_ on the allocations page
|
||||||
- Hid error messages related to no current investment in the client
|
- Hid error messages related to no current investment in the client
|
||||||
|
- Refactored the value redaction interceptor for the impersonation mode
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
import { PortfolioService } from '@ghostfolio/api/app/portfolio/portfolio.service';
|
import { PortfolioService } from '@ghostfolio/api/app/portfolio/portfolio.service';
|
||||||
import { UserService } from '@ghostfolio/api/app/user/user.service';
|
import { UserService } from '@ghostfolio/api/app/user/user.service';
|
||||||
import {
|
import { RedactValuesInResponseInterceptor } from '@ghostfolio/api/interceptors/redact-values-in-response.interceptor';
|
||||||
nullifyValuesInObject,
|
|
||||||
nullifyValuesInObjects
|
|
||||||
} from '@ghostfolio/api/helper/object.helper';
|
|
||||||
import { ImpersonationService } from '@ghostfolio/api/services/impersonation.service';
|
import { ImpersonationService } from '@ghostfolio/api/services/impersonation.service';
|
||||||
import { Accounts } from '@ghostfolio/common/interfaces';
|
import { Accounts } from '@ghostfolio/common/interfaces';
|
||||||
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
||||||
@ -22,7 +19,8 @@ import {
|
|||||||
Param,
|
Param,
|
||||||
Post,
|
Post,
|
||||||
Put,
|
Put,
|
||||||
UseGuards
|
UseGuards,
|
||||||
|
UseInterceptors
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { REQUEST } from '@nestjs/core';
|
import { REQUEST } from '@nestjs/core';
|
||||||
import { AuthGuard } from '@nestjs/passport';
|
import { AuthGuard } from '@nestjs/passport';
|
||||||
@ -85,6 +83,7 @@ export class AccountController {
|
|||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
@UseGuards(AuthGuard('jwt'))
|
@UseGuards(AuthGuard('jwt'))
|
||||||
|
@UseInterceptors(RedactValuesInResponseInterceptor)
|
||||||
public async getAllAccounts(
|
public async getAllAccounts(
|
||||||
@Headers('impersonation-id') impersonationId
|
@Headers('impersonation-id') impersonationId
|
||||||
): Promise<Accounts> {
|
): Promise<Accounts> {
|
||||||
@ -94,39 +93,15 @@ export class AccountController {
|
|||||||
this.request.user.id
|
this.request.user.id
|
||||||
);
|
);
|
||||||
|
|
||||||
let accountsWithAggregations =
|
return this.portfolioService.getAccountsWithAggregations({
|
||||||
await this.portfolioService.getAccountsWithAggregations({
|
userId: impersonationUserId || this.request.user.id,
|
||||||
userId: impersonationUserId || this.request.user.id,
|
withExcludedAccounts: true
|
||||||
withExcludedAccounts: true
|
});
|
||||||
});
|
|
||||||
|
|
||||||
if (
|
|
||||||
impersonationUserId ||
|
|
||||||
this.userService.isRestrictedView(this.request.user)
|
|
||||||
) {
|
|
||||||
accountsWithAggregations = {
|
|
||||||
...nullifyValuesInObject(accountsWithAggregations, [
|
|
||||||
'totalBalanceInBaseCurrency',
|
|
||||||
'totalValueInBaseCurrency'
|
|
||||||
]),
|
|
||||||
accounts: nullifyValuesInObjects(accountsWithAggregations.accounts, [
|
|
||||||
'balance',
|
|
||||||
'balanceInBaseCurrency',
|
|
||||||
'convertedBalance',
|
|
||||||
'fee',
|
|
||||||
'quantity',
|
|
||||||
'unitPrice',
|
|
||||||
'value',
|
|
||||||
'valueInBaseCurrency'
|
|
||||||
])
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return accountsWithAggregations;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get(':id')
|
@Get(':id')
|
||||||
@UseGuards(AuthGuard('jwt'))
|
@UseGuards(AuthGuard('jwt'))
|
||||||
|
@UseInterceptors(RedactValuesInResponseInterceptor)
|
||||||
public async getAccountById(
|
public async getAccountById(
|
||||||
@Headers('impersonation-id') impersonationId,
|
@Headers('impersonation-id') impersonationId,
|
||||||
@Param('id') id: string
|
@Param('id') id: string
|
||||||
@ -137,35 +112,13 @@ export class AccountController {
|
|||||||
this.request.user.id
|
this.request.user.id
|
||||||
);
|
);
|
||||||
|
|
||||||
let accountsWithAggregations =
|
const accountsWithAggregations =
|
||||||
await this.portfolioService.getAccountsWithAggregations({
|
await this.portfolioService.getAccountsWithAggregations({
|
||||||
filters: [{ id, type: 'ACCOUNT' }],
|
filters: [{ id, type: 'ACCOUNT' }],
|
||||||
userId: impersonationUserId || this.request.user.id,
|
userId: impersonationUserId || this.request.user.id,
|
||||||
withExcludedAccounts: true
|
withExcludedAccounts: true
|
||||||
});
|
});
|
||||||
|
|
||||||
if (
|
|
||||||
impersonationUserId ||
|
|
||||||
this.userService.isRestrictedView(this.request.user)
|
|
||||||
) {
|
|
||||||
accountsWithAggregations = {
|
|
||||||
...nullifyValuesInObject(accountsWithAggregations, [
|
|
||||||
'totalBalanceInBaseCurrency',
|
|
||||||
'totalValueInBaseCurrency'
|
|
||||||
]),
|
|
||||||
accounts: nullifyValuesInObjects(accountsWithAggregations.accounts, [
|
|
||||||
'balance',
|
|
||||||
'balanceInBaseCurrency',
|
|
||||||
'convertedBalance',
|
|
||||||
'fee',
|
|
||||||
'quantity',
|
|
||||||
'unitPrice',
|
|
||||||
'value',
|
|
||||||
'valueInBaseCurrency'
|
|
||||||
])
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return accountsWithAggregations.accounts[0];
|
return accountsWithAggregations.accounts[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -356,6 +356,7 @@ export class PortfolioController {
|
|||||||
|
|
||||||
@Get('positions')
|
@Get('positions')
|
||||||
@UseGuards(AuthGuard('jwt'))
|
@UseGuards(AuthGuard('jwt'))
|
||||||
|
@UseInterceptors(RedactValuesInResponseInterceptor)
|
||||||
@UseInterceptors(TransformDataSourceInResponseInterceptor)
|
@UseInterceptors(TransformDataSourceInResponseInterceptor)
|
||||||
public async getPositions(
|
public async getPositions(
|
||||||
@Headers('impersonation-id') impersonationId: string,
|
@Headers('impersonation-id') impersonationId: string,
|
||||||
@ -370,27 +371,11 @@ export class PortfolioController {
|
|||||||
filterByTags
|
filterByTags
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = await this.portfolioService.getPositions({
|
return this.portfolioService.getPositions({
|
||||||
dateRange,
|
dateRange,
|
||||||
filters,
|
filters,
|
||||||
impersonationId
|
impersonationId
|
||||||
});
|
});
|
||||||
|
|
||||||
if (
|
|
||||||
impersonationId ||
|
|
||||||
this.userService.isRestrictedView(this.request.user)
|
|
||||||
) {
|
|
||||||
result.positions = result.positions.map((position) => {
|
|
||||||
return nullifyValuesInObject(position, [
|
|
||||||
'grossPerformance',
|
|
||||||
'investment',
|
|
||||||
'netPerformance',
|
|
||||||
'quantity'
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('public/:accessId')
|
@Get('public/:accessId')
|
||||||
@ -460,6 +445,7 @@ export class PortfolioController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Get('position/:dataSource/:symbol')
|
@Get('position/:dataSource/:symbol')
|
||||||
|
@UseInterceptors(RedactValuesInResponseInterceptor)
|
||||||
@UseInterceptors(TransformDataSourceInRequestInterceptor)
|
@UseInterceptors(TransformDataSourceInRequestInterceptor)
|
||||||
@UseInterceptors(TransformDataSourceInResponseInterceptor)
|
@UseInterceptors(TransformDataSourceInResponseInterceptor)
|
||||||
@UseGuards(AuthGuard('jwt'))
|
@UseGuards(AuthGuard('jwt'))
|
||||||
@ -468,27 +454,13 @@ export class PortfolioController {
|
|||||||
@Param('dataSource') dataSource,
|
@Param('dataSource') dataSource,
|
||||||
@Param('symbol') symbol
|
@Param('symbol') symbol
|
||||||
): Promise<PortfolioPositionDetail> {
|
): Promise<PortfolioPositionDetail> {
|
||||||
let position = await this.portfolioService.getPosition(
|
const position = await this.portfolioService.getPosition(
|
||||||
dataSource,
|
dataSource,
|
||||||
impersonationId,
|
impersonationId,
|
||||||
symbol
|
symbol
|
||||||
);
|
);
|
||||||
|
|
||||||
if (position) {
|
if (position) {
|
||||||
if (
|
|
||||||
impersonationId ||
|
|
||||||
this.userService.isRestrictedView(this.request.user)
|
|
||||||
) {
|
|
||||||
position = nullifyValuesInObject(position, [
|
|
||||||
'grossPerformance',
|
|
||||||
'investment',
|
|
||||||
'netPerformance',
|
|
||||||
'orders',
|
|
||||||
'quantity',
|
|
||||||
'value'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return position;
|
return position;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { cloneDeep, isObject } from 'lodash';
|
import { cloneDeep, isArray, isObject } from 'lodash';
|
||||||
|
|
||||||
export function hasNotDefinedValuesInObject(aObject: Object): boolean {
|
export function hasNotDefinedValuesInObject(aObject: Object): boolean {
|
||||||
for (const key in aObject) {
|
for (const key in aObject) {
|
||||||
@ -43,15 +43,23 @@ export function redactAttributes({
|
|||||||
|
|
||||||
for (const option of options) {
|
for (const option of options) {
|
||||||
if (redactedObject.hasOwnProperty(option.attribute)) {
|
if (redactedObject.hasOwnProperty(option.attribute)) {
|
||||||
redactedObject[option.attribute] =
|
if (option.valueMap['*'] || option.valueMap['*'] === null) {
|
||||||
option.valueMap[redactedObject[option.attribute]] ??
|
redactedObject[option.attribute] = option.valueMap['*'];
|
||||||
option.valueMap['*'] ??
|
} else if (option.valueMap[redactedObject[option.attribute]]) {
|
||||||
redactedObject[option.attribute];
|
redactedObject[option.attribute] =
|
||||||
|
option.valueMap[redactedObject[option.attribute]];
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// If the attribute is not present on the current object,
|
// If the attribute is not present on the current object,
|
||||||
// check if it exists on any nested objects
|
// check if it exists on any nested objects
|
||||||
for (const property in redactedObject) {
|
for (const property in redactedObject) {
|
||||||
if (typeof redactedObject[property] === 'object') {
|
if (isArray(redactedObject[property])) {
|
||||||
|
redactedObject[property] = redactedObject[property].map(
|
||||||
|
(currentObject) => {
|
||||||
|
return redactAttributes({ options, object: currentObject });
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else if (isObject(redactedObject[property])) {
|
||||||
// Recursively call the function on the nested object
|
// Recursively call the function on the nested object
|
||||||
redactedObject[property] = redactAttributes({
|
redactedObject[property] = redactAttributes({
|
||||||
options,
|
options,
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
|
|
||||||
import { UserService } from '@ghostfolio/api/app/user/user.service';
|
import { UserService } from '@ghostfolio/api/app/user/user.service';
|
||||||
|
import { redactAttributes } from '@ghostfolio/api/helper/object.helper';
|
||||||
import {
|
import {
|
||||||
CallHandler,
|
CallHandler,
|
||||||
ExecutionContext,
|
ExecutionContext,
|
||||||
@ -28,59 +28,35 @@ export class RedactValuesInResponseInterceptor<T>
|
|||||||
hasImpersonationId ||
|
hasImpersonationId ||
|
||||||
this.userService.isRestrictedView(request.user)
|
this.userService.isRestrictedView(request.user)
|
||||||
) {
|
) {
|
||||||
if (data.accounts) {
|
data = redactAttributes({
|
||||||
for (const accountId of Object.keys(data.accounts)) {
|
object: data,
|
||||||
if (data.accounts[accountId]?.balance !== undefined) {
|
options: [
|
||||||
data.accounts[accountId].balance = null;
|
'balance',
|
||||||
}
|
'balanceInBaseCurrency',
|
||||||
}
|
'comment',
|
||||||
}
|
'convertedBalance',
|
||||||
|
'fee',
|
||||||
if (data.activities) {
|
'feeInBaseCurrency',
|
||||||
data.activities = data.activities.map((activity: Activity) => {
|
'filteredValueInBaseCurrency',
|
||||||
if (activity.Account?.balance !== undefined) {
|
'grossPerformance',
|
||||||
activity.Account.balance = null;
|
'investment',
|
||||||
}
|
'netPerformance',
|
||||||
|
'quantity',
|
||||||
if (activity.comment !== undefined) {
|
'symbolMapping',
|
||||||
activity.comment = null;
|
'totalBalanceInBaseCurrency',
|
||||||
}
|
'totalValueInBaseCurrency',
|
||||||
|
'unitPrice',
|
||||||
if (activity.fee !== undefined) {
|
'value',
|
||||||
activity.fee = null;
|
'valueInBaseCurrency'
|
||||||
}
|
].map((attribute) => {
|
||||||
|
return {
|
||||||
if (activity.feeInBaseCurrency !== undefined) {
|
attribute,
|
||||||
activity.feeInBaseCurrency = null;
|
valueMap: {
|
||||||
}
|
'*': null
|
||||||
|
}
|
||||||
if (activity.quantity !== undefined) {
|
};
|
||||||
activity.quantity = null;
|
})
|
||||||
}
|
});
|
||||||
|
|
||||||
if (activity.unitPrice !== undefined) {
|
|
||||||
activity.unitPrice = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (activity.value !== undefined) {
|
|
||||||
activity.value = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (activity.valueInBaseCurrency !== undefined) {
|
|
||||||
activity.valueInBaseCurrency = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return activity;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.filteredValueInBaseCurrency) {
|
|
||||||
data.filteredValueInBaseCurrency = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.totalValueInBaseCurrency) {
|
|
||||||
data.totalValueInBaseCurrency = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user