diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e3ebad46..8fc116064 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Added + +- 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 - Refreshed the cryptocurrencies list ## 2.218.0 - 2025-11-20 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/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 + ] }); } 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; }