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
+ ]
});
}