From f70d71d5bd1d34f943e18f8c26f46cbb2937d5c4 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Fri, 2 May 2025 16:46:43 +0200 Subject: [PATCH] Feature/improve watchlist for impersonation mode (#4632) * Improve watchlist for impersonation mode * Update changelog --- CHANGELOG.md | 1 + .../watchlist/watchlist.controller.ts | 13 ++++++-- .../endpoints/watchlist/watchlist.module.ts | 2 ++ .../home-watchlist.component.ts | 30 ++++++++++++++----- .../home-watchlist/home-watchlist.html | 2 +- 5 files changed, 37 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 85df415b5..7bbdaf434 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 - Extended the watchlist by the date of the last all time high, the current change to the all time high and the current market condition (experimental) +- Added support for the impersonation mode in the watchlist (experimental) ### Changed diff --git a/apps/api/src/app/endpoints/watchlist/watchlist.controller.ts b/apps/api/src/app/endpoints/watchlist/watchlist.controller.ts index c9e41d5d3..8d9d322a8 100644 --- a/apps/api/src/app/endpoints/watchlist/watchlist.controller.ts +++ b/apps/api/src/app/endpoints/watchlist/watchlist.controller.ts @@ -2,6 +2,8 @@ import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorat import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard'; import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.interceptor'; import { TransformDataSourceInResponseInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-response/transform-data-source-in-response.interceptor'; +import { ImpersonationService } from '@ghostfolio/api/services/impersonation/impersonation.service'; +import { HEADER_KEY_IMPERSONATION } from '@ghostfolio/common/config'; import { WatchlistResponse } from '@ghostfolio/common/interfaces'; import { permissions } from '@ghostfolio/common/permissions'; import { RequestWithUser } from '@ghostfolio/common/types'; @@ -11,6 +13,7 @@ import { Controller, Delete, Get, + Headers, HttpException, Inject, Param, @@ -29,6 +32,7 @@ import { WatchlistService } from './watchlist.service'; @Controller('watchlist') export class WatchlistController { public constructor( + private readonly impersonationService: ImpersonationService, @Inject(REQUEST) private readonly request: RequestWithUser, private readonly watchlistService: WatchlistService ) {} @@ -79,9 +83,14 @@ export class WatchlistController { @HasPermission(permissions.readWatchlist) @UseGuards(AuthGuard('jwt'), HasPermissionGuard) @UseInterceptors(TransformDataSourceInResponseInterceptor) - public async getWatchlistItems(): Promise { + public async getWatchlistItems( + @Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string + ): Promise { + const impersonationUserId = + await this.impersonationService.validateImpersonationId(impersonationId); + const watchlist = await this.watchlistService.getWatchlistItems( - this.request.user.id + impersonationUserId || this.request.user.id ); return { diff --git a/apps/api/src/app/endpoints/watchlist/watchlist.module.ts b/apps/api/src/app/endpoints/watchlist/watchlist.module.ts index a2d32d1d2..ce9ae12bb 100644 --- a/apps/api/src/app/endpoints/watchlist/watchlist.module.ts +++ b/apps/api/src/app/endpoints/watchlist/watchlist.module.ts @@ -2,6 +2,7 @@ import { TransformDataSourceInRequestModule } from '@ghostfolio/api/interceptors import { TransformDataSourceInResponseModule } from '@ghostfolio/api/interceptors/transform-data-source-in-response/transform-data-source-in-response.module'; import { BenchmarkModule } from '@ghostfolio/api/services/benchmark/benchmark.module'; import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module'; +import { ImpersonationModule } from '@ghostfolio/api/services/impersonation/impersonation.module'; import { MarketDataModule } from '@ghostfolio/api/services/market-data/market-data.module'; import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module'; import { DataGatheringModule } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.module'; @@ -18,6 +19,7 @@ import { WatchlistService } from './watchlist.service'; BenchmarkModule, DataGatheringModule, DataProviderModule, + ImpersonationModule, MarketDataModule, PrismaModule, SymbolProfileModule, diff --git a/apps/client/src/app/components/home-watchlist/home-watchlist.component.ts b/apps/client/src/app/components/home-watchlist/home-watchlist.component.ts index efad2fef5..5c0b3fa50 100644 --- a/apps/client/src/app/components/home-watchlist/home-watchlist.component.ts +++ b/apps/client/src/app/components/home-watchlist/home-watchlist.component.ts @@ -1,4 +1,5 @@ import { DataService } from '@ghostfolio/client/services/data.service'; +import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; import { AssetProfileIdentifier, @@ -45,6 +46,7 @@ import { CreateWatchlistItemDialogParams } from './create-watchlist-item-dialog/ }) export class HomeWatchlistComponent implements OnDestroy, OnInit { public deviceType: string; + public hasImpersonationId: boolean; public hasPermissionToCreateWatchlistItem: boolean; public hasPermissionToDeleteWatchlistItem: boolean; public user: User; @@ -57,12 +59,20 @@ export class HomeWatchlistComponent implements OnDestroy, OnInit { private dataService: DataService, private deviceService: DeviceDetectorService, private dialog: MatDialog, + private impersonationStorageService: ImpersonationStorageService, private route: ActivatedRoute, private router: Router, private userService: UserService ) { this.deviceType = this.deviceService.getDeviceInfo().deviceType; + this.impersonationStorageService + .onChangeHasImpersonation() + .pipe(takeUntil(this.unsubscribeSubject)) + .subscribe((impersonationId) => { + this.hasImpersonationId = !!impersonationId; + }); + this.route.queryParams .pipe(takeUntil(this.unsubscribeSubject)) .subscribe((params) => { @@ -77,14 +87,18 @@ export class HomeWatchlistComponent implements OnDestroy, OnInit { if (state?.user) { this.user = state.user; - this.hasPermissionToCreateWatchlistItem = hasPermission( - this.user.permissions, - permissions.createWatchlistItem - ); - this.hasPermissionToDeleteWatchlistItem = hasPermission( - this.user.permissions, - permissions.deleteWatchlistItem - ); + this.hasPermissionToCreateWatchlistItem = + !this.hasImpersonationId && + hasPermission( + this.user.permissions, + permissions.createWatchlistItem + ); + this.hasPermissionToDeleteWatchlistItem = + !this.hasImpersonationId && + hasPermission( + this.user.permissions, + permissions.deleteWatchlistItem + ); this.changeDetectorRef.markForCheck(); } diff --git a/apps/client/src/app/components/home-watchlist/home-watchlist.html b/apps/client/src/app/components/home-watchlist/home-watchlist.html index d290a4a2d..9149eab91 100644 --- a/apps/client/src/app/components/home-watchlist/home-watchlist.html +++ b/apps/client/src/app/components/home-watchlist/home-watchlist.html @@ -20,7 +20,7 @@ -@if (hasPermissionToCreateWatchlistItem) { +@if (!hasImpersonationId && hasPermissionToCreateWatchlistItem) {