From 6f9e466aa2fcfd061f6f70936e9b617afdbd1a50 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 22 Nov 2025 10:19:30 +0100 Subject: [PATCH 1/2] Feature/add authentication method to user detail dialog (#5970) * Extend user detail dialog * Update changelog --- CHANGELOG.md | 6 +++++ apps/api/src/app/admin/admin.service.ts | 4 ++- .../user-detail-dialog.html | 27 ++++++++++++++++--- .../lib/interfaces/admin-user.interface.ts | 4 ++- 4 files changed, 36 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ca25a5a1..e9003f94e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ 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/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Added + +- Extended the user detail dialog of the admin control panel’s users section by the authentication method + ## 2.218.0 - 2025-11-20 ### Added diff --git a/apps/api/src/app/admin/admin.service.ts b/apps/api/src/app/admin/admin.service.ts index 6b29b141a..0a6df7647 100644 --- a/apps/api/src/app/admin/admin.service.ts +++ b/apps/api/src/app/admin/admin.service.ts @@ -876,6 +876,7 @@ export class AdminService { }, createdAt: true, id: true, + provider: true, role: true, subscriptions: { orderBy: { @@ -892,7 +893,7 @@ export class AdminService { }); return usersWithAnalytics.map( - ({ _count, analytics, createdAt, id, role, subscriptions }) => { + ({ _count, analytics, createdAt, id, provider, role, subscriptions }) => { const daysSinceRegistration = differenceInDays(new Date(), createdAt) + 1; const engagement = analytics @@ -909,6 +910,7 @@ export class AdminService { createdAt, engagement, id, + provider, role, subscription, accountCount: _count.accounts || 0, diff --git a/apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html b/apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html index fcefee4f0..551f9b943 100644 --- a/apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html +++ b/apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html @@ -23,17 +23,38 @@
+
+ Authentication +
Role
- @if (data.hasPermissionForSubscription) { +
+ + @if (data.hasPermissionForSubscription) { +
+
+ Membership +
Country
- } -
+ + }
diff --git a/libs/common/src/lib/interfaces/admin-user.interface.ts b/libs/common/src/lib/interfaces/admin-user.interface.ts index 872abca90..4cb02b16e 100644 --- a/libs/common/src/lib/interfaces/admin-user.interface.ts +++ b/libs/common/src/lib/interfaces/admin-user.interface.ts @@ -1,4 +1,4 @@ -import { Role } from '@prisma/client'; +import { Provider, Role, Subscription } from '@prisma/client'; export interface AdminUser { accountCount: number; @@ -9,5 +9,7 @@ export interface AdminUser { engagement: number; id: string; lastActivity: Date; + provider: Provider; role: Role; + subscription?: Subscription; } 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 2/2] 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 + ] }); }