Feature/introduce filters in account endpoint (#3764)
* Introduce filters in acount endpoint * Integrate endpoint in holding detail dialog * Update changelog --------- Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
This commit is contained in:
parent
8735fc3fad
commit
323cfbfcaa
10
CHANGELOG.md
10
CHANGELOG.md
@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Introduced filters (`dataSource` and `symbol`) in the accounts endpoint
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Switched to the accounts endpoint in the holding detail dialog
|
||||||
|
|
||||||
## 2.107.1 - 2024-09-12
|
## 2.107.1 - 2024-09-12
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
@ -3,6 +3,8 @@ import { PortfolioService } from '@ghostfolio/api/app/portfolio/portfolio.servic
|
|||||||
import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator';
|
import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator';
|
||||||
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard';
|
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard';
|
||||||
import { RedactValuesInResponseInterceptor } from '@ghostfolio/api/interceptors/redact-values-in-response/redact-values-in-response.interceptor';
|
import { RedactValuesInResponseInterceptor } from '@ghostfolio/api/interceptors/redact-values-in-response/redact-values-in-response.interceptor';
|
||||||
|
import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.interceptor';
|
||||||
|
import { ApiService } from '@ghostfolio/api/services/api/api.service';
|
||||||
import { ImpersonationService } from '@ghostfolio/api/services/impersonation/impersonation.service';
|
import { ImpersonationService } from '@ghostfolio/api/services/impersonation/impersonation.service';
|
||||||
import { HEADER_KEY_IMPERSONATION } from '@ghostfolio/common/config';
|
import { HEADER_KEY_IMPERSONATION } from '@ghostfolio/common/config';
|
||||||
import {
|
import {
|
||||||
@ -26,6 +28,7 @@ import {
|
|||||||
Param,
|
Param,
|
||||||
Post,
|
Post,
|
||||||
Put,
|
Put,
|
||||||
|
Query,
|
||||||
UseGuards,
|
UseGuards,
|
||||||
UseInterceptors
|
UseInterceptors
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
@ -44,6 +47,7 @@ export class AccountController {
|
|||||||
public constructor(
|
public constructor(
|
||||||
private readonly accountBalanceService: AccountBalanceService,
|
private readonly accountBalanceService: AccountBalanceService,
|
||||||
private readonly accountService: AccountService,
|
private readonly accountService: AccountService,
|
||||||
|
private readonly apiService: ApiService,
|
||||||
private readonly impersonationService: ImpersonationService,
|
private readonly impersonationService: ImpersonationService,
|
||||||
private readonly portfolioService: PortfolioService,
|
private readonly portfolioService: PortfolioService,
|
||||||
@Inject(REQUEST) private readonly request: RequestWithUser
|
@Inject(REQUEST) private readonly request: RequestWithUser
|
||||||
@ -84,13 +88,22 @@ export class AccountController {
|
|||||||
@Get()
|
@Get()
|
||||||
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
|
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
|
||||||
@UseInterceptors(RedactValuesInResponseInterceptor)
|
@UseInterceptors(RedactValuesInResponseInterceptor)
|
||||||
|
@UseInterceptors(TransformDataSourceInRequestInterceptor)
|
||||||
public async getAllAccounts(
|
public async getAllAccounts(
|
||||||
@Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId
|
@Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId,
|
||||||
|
@Query('dataSource') filterByDataSource?: string,
|
||||||
|
@Query('symbol') filterBySymbol?: string
|
||||||
): Promise<Accounts> {
|
): Promise<Accounts> {
|
||||||
const impersonationUserId =
|
const impersonationUserId =
|
||||||
await this.impersonationService.validateImpersonationId(impersonationId);
|
await this.impersonationService.validateImpersonationId(impersonationId);
|
||||||
|
|
||||||
|
const filters = this.apiService.buildFiltersFromQueryParams({
|
||||||
|
filterByDataSource,
|
||||||
|
filterBySymbol
|
||||||
|
});
|
||||||
|
|
||||||
return this.portfolioService.getAccountsWithAggregations({
|
return this.portfolioService.getAccountsWithAggregations({
|
||||||
|
filters,
|
||||||
userId: impersonationUserId || this.request.user.id,
|
userId: impersonationUserId || this.request.user.id,
|
||||||
withExcludedAccounts: true
|
withExcludedAccounts: true
|
||||||
});
|
});
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { AccountBalanceModule } from '@ghostfolio/api/app/account-balance/account-balance.module';
|
import { AccountBalanceModule } from '@ghostfolio/api/app/account-balance/account-balance.module';
|
||||||
import { PortfolioModule } from '@ghostfolio/api/app/portfolio/portfolio.module';
|
import { PortfolioModule } from '@ghostfolio/api/app/portfolio/portfolio.module';
|
||||||
import { RedactValuesInResponseModule } from '@ghostfolio/api/interceptors/redact-values-in-response/redact-values-in-response.module';
|
import { RedactValuesInResponseModule } from '@ghostfolio/api/interceptors/redact-values-in-response/redact-values-in-response.module';
|
||||||
|
import { ApiModule } from '@ghostfolio/api/services/api/api.module';
|
||||||
import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module';
|
import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module';
|
||||||
import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.module';
|
import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.module';
|
||||||
import { ImpersonationModule } from '@ghostfolio/api/services/impersonation/impersonation.module';
|
import { ImpersonationModule } from '@ghostfolio/api/services/impersonation/impersonation.module';
|
||||||
@ -16,6 +17,7 @@ import { AccountService } from './account.service';
|
|||||||
exports: [AccountService],
|
exports: [AccountService],
|
||||||
imports: [
|
imports: [
|
||||||
AccountBalanceModule,
|
AccountBalanceModule,
|
||||||
|
ApiModule,
|
||||||
ConfigurationModule,
|
ConfigurationModule,
|
||||||
ExchangeRateDataModule,
|
ExchangeRateDataModule,
|
||||||
ImpersonationModule,
|
ImpersonationModule,
|
||||||
|
@ -115,12 +115,33 @@ export class PortfolioService {
|
|||||||
}): Promise<AccountWithValue[]> {
|
}): Promise<AccountWithValue[]> {
|
||||||
const where: Prisma.AccountWhereInput = { userId };
|
const where: Prisma.AccountWhereInput = { userId };
|
||||||
|
|
||||||
const accountFilter = filters?.find(({ type }) => {
|
const filterByAccount = filters?.find(({ type }) => {
|
||||||
return type === 'ACCOUNT';
|
return type === 'ACCOUNT';
|
||||||
});
|
})?.id;
|
||||||
|
|
||||||
if (accountFilter) {
|
const filterByDataSource = filters?.find(({ type }) => {
|
||||||
where.id = accountFilter.id;
|
return type === 'DATA_SOURCE';
|
||||||
|
})?.id;
|
||||||
|
|
||||||
|
const filterBySymbol = filters?.find(({ type }) => {
|
||||||
|
return type === 'SYMBOL';
|
||||||
|
})?.id;
|
||||||
|
|
||||||
|
if (filterByAccount) {
|
||||||
|
where.id = filterByAccount;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filterByDataSource && filterBySymbol) {
|
||||||
|
where.Order = {
|
||||||
|
some: {
|
||||||
|
SymbolProfile: {
|
||||||
|
AND: [
|
||||||
|
{ dataSource: <DataSource>filterByDataSource },
|
||||||
|
{ symbol: filterBySymbol }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const [accounts, details] = await Promise.all([
|
const [accounts, details] = await Promise.all([
|
||||||
|
@ -9,6 +9,7 @@ import { DATE_FORMAT, downloadAsFile } from '@ghostfolio/common/helper';
|
|||||||
import {
|
import {
|
||||||
DataProviderInfo,
|
DataProviderInfo,
|
||||||
EnhancedSymbolProfile,
|
EnhancedSymbolProfile,
|
||||||
|
Filter,
|
||||||
LineChartItem,
|
LineChartItem,
|
||||||
User
|
User
|
||||||
} from '@ghostfolio/common/interfaces';
|
} from '@ghostfolio/common/interfaces';
|
||||||
@ -152,6 +153,11 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit {
|
|||||||
tags: <string[]>[]
|
tags: <string[]>[]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const filters: Filter[] = [
|
||||||
|
{ id: this.data.dataSource, type: 'DATA_SOURCE' },
|
||||||
|
{ id: this.data.symbol, type: 'SYMBOL' }
|
||||||
|
];
|
||||||
|
|
||||||
this.tagsAvailable = tags.map(({ id, name }) => {
|
this.tagsAvailable = tags.map(({ id, name }) => {
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
@ -173,12 +179,20 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit {
|
|||||||
.subscribe();
|
.subscribe();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.dataService
|
||||||
|
.fetchAccounts({
|
||||||
|
filters
|
||||||
|
})
|
||||||
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
|
.subscribe(({ accounts }) => {
|
||||||
|
this.accounts = accounts;
|
||||||
|
|
||||||
|
this.changeDetectorRef.markForCheck();
|
||||||
|
});
|
||||||
|
|
||||||
this.dataService
|
this.dataService
|
||||||
.fetchActivities({
|
.fetchActivities({
|
||||||
filters: [
|
filters,
|
||||||
{ id: this.data.dataSource, type: 'DATA_SOURCE' },
|
|
||||||
{ id: this.data.symbol, type: 'SYMBOL' }
|
|
||||||
],
|
|
||||||
sortColumn: this.sortColumn,
|
sortColumn: this.sortColumn,
|
||||||
sortDirection: this.sortDirection
|
sortDirection: this.sortDirection
|
||||||
})
|
})
|
||||||
@ -197,7 +211,6 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit {
|
|||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
.subscribe(
|
.subscribe(
|
||||||
({
|
({
|
||||||
accounts,
|
|
||||||
averagePrice,
|
averagePrice,
|
||||||
dataProviderInfo,
|
dataProviderInfo,
|
||||||
dividendInBaseCurrency,
|
dividendInBaseCurrency,
|
||||||
@ -219,7 +232,6 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit {
|
|||||||
transactionCount,
|
transactionCount,
|
||||||
value
|
value
|
||||||
}) => {
|
}) => {
|
||||||
this.accounts = accounts;
|
|
||||||
this.averagePrice = averagePrice;
|
this.averagePrice = averagePrice;
|
||||||
this.benchmarkDataItems = [];
|
this.benchmarkDataItems = [];
|
||||||
this.countries = {};
|
this.countries = {};
|
||||||
|
@ -173,8 +173,10 @@ export class DataService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public fetchAccounts() {
|
public fetchAccounts({ filters }: { filters?: Filter[] } = {}) {
|
||||||
return this.http.get<Accounts>('/api/v1/account');
|
const params = this.buildFiltersAsQueryParams({ filters });
|
||||||
|
|
||||||
|
return this.http.get<Accounts>('/api/v1/account', { params });
|
||||||
}
|
}
|
||||||
|
|
||||||
public fetchActivities({
|
public fetchActivities({
|
||||||
|
Loading…
x
Reference in New Issue
Block a user