From 64b47414509466ae7339c5854b0b9b41f6d3f263 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 22 Nov 2025 12:14:10 +0100 Subject: [PATCH] Feature/improve transform data source in request and response interceptor (#5972) * Resolve data source of GHOSTFOLIO data provider * Update changelog --- CHANGELOG.md | 5 ++ apps/api/src/app/export/export.controller.ts | 2 + ...form-data-source-in-request.interceptor.ts | 16 +++++- ...orm-data-source-in-response.interceptor.ts | 54 +++++++++++++------ 4 files changed, 59 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e9003f94e..4b3dd89bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Extended the user detail dialog of the admin control panel’s users section by the authentication method +### Changed + +- Resolved the data source of the `GHOSTFOLIO` data provider in the export functionality +- Resolved the data source of the `GHOSTFOLIO` data provider in the import functionality + ## 2.218.0 - 2025-11-20 ### Added diff --git a/apps/api/src/app/export/export.controller.ts b/apps/api/src/app/export/export.controller.ts index 5446f8789..6fda8f17f 100644 --- a/apps/api/src/app/export/export.controller.ts +++ b/apps/api/src/app/export/export.controller.ts @@ -1,5 +1,6 @@ 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 { ApiService } from '@ghostfolio/api/services/api/api.service'; import { ExportResponse } from '@ghostfolio/common/interfaces'; import type { RequestWithUser } from '@ghostfolio/common/types'; @@ -28,6 +29,7 @@ export class ExportController { @Get() @UseGuards(AuthGuard('jwt'), HasPermissionGuard) @UseInterceptors(TransformDataSourceInRequestInterceptor) + @UseInterceptors(TransformDataSourceInResponseInterceptor) public async export( @Query('accounts') filterByAccounts?: string, @Query('activityIds') filterByActivityIds?: string, diff --git a/apps/api/src/interceptors/transform-data-source-in-request/transform-data-source-in-request.interceptor.ts b/apps/api/src/interceptors/transform-data-source-in-request/transform-data-source-in-request.interceptor.ts index 1600bd137..3931f362c 100644 --- a/apps/api/src/interceptors/transform-data-source-in-request/transform-data-source-in-request.interceptor.ts +++ b/apps/api/src/interceptors/transform-data-source-in-request/transform-data-source-in-request.interceptor.ts @@ -27,9 +27,23 @@ export class TransformDataSourceInRequestInterceptor if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) { if (request.body?.activities) { + const dataSourceGhostfolioDataProvider = this.configurationService.get( + 'DATA_SOURCES_GHOSTFOLIO_DATA_PROVIDER' + )?.[0]; + request.body.activities = request.body.activities.map((activity) => { if (DataSource[activity.dataSource]) { - return activity; + if ( + activity.dataSource === 'GHOSTFOLIO' && + dataSourceGhostfolioDataProvider + ) { + return { + ...activity, + dataSource: dataSourceGhostfolioDataProvider + }; + } else { + return activity; + } } else { return { ...activity, diff --git a/apps/api/src/interceptors/transform-data-source-in-response/transform-data-source-in-response.interceptor.ts b/apps/api/src/interceptors/transform-data-source-in-response/transform-data-source-in-response.interceptor.ts index fcbf3e76e..fea5d6fe6 100644 --- a/apps/api/src/interceptors/transform-data-source-in-response/transform-data-source-in-response.interceptor.ts +++ b/apps/api/src/interceptors/transform-data-source-in-response/transform-data-source-in-response.interceptor.ts @@ -16,36 +16,56 @@ import { map } from 'rxjs/operators'; export class TransformDataSourceInResponseInterceptor implements NestInterceptor { + private encodedDataSourceMap: { + [dataSource: string]: string; + } = {}; + public constructor( private readonly configurationService: ConfigurationService - ) {} + ) { + if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) { + this.encodedDataSourceMap = Object.keys(DataSource).reduce( + (encodedDataSourceMap, dataSource) => { + if (!['GHOSTFOLIO', 'MANUAL'].includes(dataSource)) { + encodedDataSourceMap[dataSource] = encodeDataSource( + DataSource[dataSource] + ); + } + + return encodedDataSourceMap; + }, + {} + ); + } + } public intercept( - _context: ExecutionContext, + context: ExecutionContext, next: CallHandler ): Observable { + const isExportMode = context.getClass().name === 'ExportController'; + return next.handle().pipe( map((data: any) => { if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) { + const valueMap = this.encodedDataSourceMap; + + if (isExportMode) { + for (const dataSource of this.configurationService.get( + 'DATA_SOURCES_GHOSTFOLIO_DATA_PROVIDER' + )) { + valueMap[dataSource] = 'GHOSTFOLIO'; + } + } + data = redactAttributes({ + object: data, options: [ { - attribute: 'dataSource', - valueMap: Object.keys(DataSource).reduce( - (valueMap, dataSource) => { - if (!['MANUAL'].includes(dataSource)) { - valueMap[dataSource] = encodeDataSource( - DataSource[dataSource] - ); - } - - return valueMap; - }, - {} - ) + valueMap, + attribute: 'dataSource' } - ], - object: data + ] }); }