Browse Source

Feature/improve transform data source in request and response interceptor (#5972)

* Resolve data source of GHOSTFOLIO data provider

* Update changelog
pull/5971/head^2
Thomas Kaul 7 days ago
committed by GitHub
parent
commit
64b4741450
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 5
      CHANGELOG.md
  2. 2
      apps/api/src/app/export/export.controller.ts
  3. 14
      apps/api/src/interceptors/transform-data-source-in-request/transform-data-source-in-request.interceptor.ts
  4. 54
      apps/api/src/interceptors/transform-data-source-in-response/transform-data-source-in-response.interceptor.ts

5
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 - 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 ## 2.218.0 - 2025-11-20
### Added ### Added

2
apps/api/src/app/export/export.controller.ts

@ -1,5 +1,6 @@
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard'; 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 { 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 { ApiService } from '@ghostfolio/api/services/api/api.service';
import { ExportResponse } from '@ghostfolio/common/interfaces'; import { ExportResponse } from '@ghostfolio/common/interfaces';
import type { RequestWithUser } from '@ghostfolio/common/types'; import type { RequestWithUser } from '@ghostfolio/common/types';
@ -28,6 +29,7 @@ export class ExportController {
@Get() @Get()
@UseGuards(AuthGuard('jwt'), HasPermissionGuard) @UseGuards(AuthGuard('jwt'), HasPermissionGuard)
@UseInterceptors(TransformDataSourceInRequestInterceptor) @UseInterceptors(TransformDataSourceInRequestInterceptor)
@UseInterceptors(TransformDataSourceInResponseInterceptor)
public async export( public async export(
@Query('accounts') filterByAccounts?: string, @Query('accounts') filterByAccounts?: string,
@Query('activityIds') filterByActivityIds?: string, @Query('activityIds') filterByActivityIds?: string,

14
apps/api/src/interceptors/transform-data-source-in-request/transform-data-source-in-request.interceptor.ts

@ -27,9 +27,23 @@ export class TransformDataSourceInRequestInterceptor<T>
if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) { if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) {
if (request.body?.activities) { if (request.body?.activities) {
const dataSourceGhostfolioDataProvider = this.configurationService.get(
'DATA_SOURCES_GHOSTFOLIO_DATA_PROVIDER'
)?.[0];
request.body.activities = request.body.activities.map((activity) => { request.body.activities = request.body.activities.map((activity) => {
if (DataSource[activity.dataSource]) { if (DataSource[activity.dataSource]) {
if (
activity.dataSource === 'GHOSTFOLIO' &&
dataSourceGhostfolioDataProvider
) {
return {
...activity,
dataSource: dataSourceGhostfolioDataProvider
};
} else {
return activity; return activity;
}
} else { } else {
return { return {
...activity, ...activity,

54
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<T> export class TransformDataSourceInResponseInterceptor<T>
implements NestInterceptor<T, any> implements NestInterceptor<T, any>
{ {
private encodedDataSourceMap: {
[dataSource: string]: string;
} = {};
public constructor( public constructor(
private readonly configurationService: ConfigurationService 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( public intercept(
_context: ExecutionContext, context: ExecutionContext,
next: CallHandler<T> next: CallHandler<T>
): Observable<any> { ): Observable<any> {
const isExportMode = context.getClass().name === 'ExportController';
return next.handle().pipe( return next.handle().pipe(
map((data: any) => { map((data: any) => {
if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) { 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({ data = redactAttributes({
object: data,
options: [ options: [
{ {
attribute: 'dataSource', valueMap,
valueMap: Object.keys(DataSource).reduce( attribute: 'dataSource'
(valueMap, dataSource) => {
if (!['MANUAL'].includes(dataSource)) {
valueMap[dataSource] = encodeDataSource(
DataSource[dataSource]
);
}
return valueMap;
},
{}
)
} }
], ]
object: data
}); });
} }

Loading…
Cancel
Save