From 8236091477f9e8ea085cbcbcefe6a55171b500c5 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 7 Oct 2023 19:30:28 +0200 Subject: [PATCH] Feature/add support for search query in portfolio position endpoint (#2443) * Introduce search query filter * Update changelog --- CHANGELOG.md | 1 + .../src/app/portfolio/portfolio.controller.ts | 2 ++ .../src/app/portfolio/portfolio.service.ts | 20 ++++++++++++++++--- apps/api/src/services/api/api.service.ts | 7 +++++++ .../enhanced-symbol-profile.interface.ts | 1 + .../src/lib/interfaces/filter.interface.ts | 1 + 6 files changed, 29 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 17ff05e89..147a48b1a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Added support for notes in the activities import +- Added support for a search query in the portfolio position endpoint - Added the application version to the endpoint `GET api/v1/admin` - Introduced a carousel component for the testimonial section on the landing page diff --git a/apps/api/src/app/portfolio/portfolio.controller.ts b/apps/api/src/app/portfolio/portfolio.controller.ts index ff3161280..6e42b1304 100644 --- a/apps/api/src/app/portfolio/portfolio.controller.ts +++ b/apps/api/src/app/portfolio/portfolio.controller.ts @@ -391,12 +391,14 @@ export class PortfolioController { @Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string, @Query('accounts') filterByAccounts?: string, @Query('assetClasses') filterByAssetClasses?: string, + @Query('query') filterBySearchQuery?: string, @Query('range') dateRange: DateRange = 'max', @Query('tags') filterByTags?: string ): Promise { const filters = this.apiService.buildFiltersFromQueryParams({ filterByAccounts, filterByAssetClasses, + filterBySearchQuery, filterByTags }); diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts index b9d6cef1b..6a60b5e6a 100644 --- a/apps/api/src/app/portfolio/portfolio.service.ts +++ b/apps/api/src/app/portfolio/portfolio.service.ts @@ -1014,6 +1014,9 @@ export class PortfolioService { filters?: Filter[]; impersonationId: string; }): Promise<{ hasErrors: boolean; positions: Position[] }> { + const searchQuery = filters.find(({ type }) => { + return type === 'SEARCH_QUERY'; + })?.id; const userId = await this.getUserId(impersonationId, this.request.user.id); const { portfolioOrders, transactionPoints } = @@ -1042,9 +1045,9 @@ export class PortfolioService { const currentPositions = await portfolioCalculator.getCurrentPositions(startDate); - const positions = currentPositions.positions.filter( - (item) => !item.quantity.eq(0) - ); + let positions = currentPositions.positions.filter(({ quantity }) => { + return !quantity.eq(0); + }); const dataGatheringItems = positions.map(({ dataSource, symbol }) => { return { @@ -1067,6 +1070,17 @@ export class PortfolioService { symbolProfileMap[symbolProfile.symbol] = symbolProfile; } + if (searchQuery) { + positions = positions.filter(({ symbol }) => { + const enhancedSymbolProfile = symbolProfileMap[symbol]; + + return ( + enhancedSymbolProfile.isin?.toLowerCase().startsWith(searchQuery) || + enhancedSymbolProfile.name?.toLowerCase().startsWith(searchQuery) + ); + }); + } + return { hasErrors: currentPositions.hasErrors, positions: positions.map((position) => { diff --git a/apps/api/src/services/api/api.service.ts b/apps/api/src/services/api/api.service.ts index 2a6b1fb06..204aa030e 100644 --- a/apps/api/src/services/api/api.service.ts +++ b/apps/api/src/services/api/api.service.ts @@ -8,14 +8,17 @@ export class ApiService { public buildFiltersFromQueryParams({ filterByAccounts, filterByAssetClasses, + filterBySearchQuery, filterByTags }: { filterByAccounts?: string; filterByAssetClasses?: string; + filterBySearchQuery?: string; filterByTags?: string; }): Filter[] { const accountIds = filterByAccounts?.split(',') ?? []; const assetClasses = filterByAssetClasses?.split(',') ?? []; + const searchQuery = filterBySearchQuery?.toLowerCase(); const tagIds = filterByTags?.split(',') ?? []; return [ @@ -31,6 +34,10 @@ export class ApiService { type: 'ASSET_CLASS' }; }), + { + id: searchQuery, + type: 'SEARCH_QUERY' + }, ...tagIds.map((tagId) => { return { id: tagId, diff --git a/libs/common/src/lib/interfaces/enhanced-symbol-profile.interface.ts b/libs/common/src/lib/interfaces/enhanced-symbol-profile.interface.ts index 5b72d9ce7..3bf914eaa 100644 --- a/libs/common/src/lib/interfaces/enhanced-symbol-profile.interface.ts +++ b/libs/common/src/lib/interfaces/enhanced-symbol-profile.interface.ts @@ -15,6 +15,7 @@ export interface EnhancedSymbolProfile { dataSource: DataSource; dateOfFirstActivity?: Date; id: string; + isin: string | null; name: string | null; scraperConfiguration?: ScraperConfiguration | null; sectors: Sector[]; diff --git a/libs/common/src/lib/interfaces/filter.interface.ts b/libs/common/src/lib/interfaces/filter.interface.ts index a6bc17c4f..356b3add7 100644 --- a/libs/common/src/lib/interfaces/filter.interface.ts +++ b/libs/common/src/lib/interfaces/filter.interface.ts @@ -6,6 +6,7 @@ export interface Filter { | 'ASSET_CLASS' | 'ASSET_SUB_CLASS' | 'PRESET_ID' + | 'SEARCH_QUERY' | 'SYMBOL' | 'TAG'; }