From 3eaba49f19859d899b133407e10ea6a46a183539 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Mon, 3 Aug 2026 19:48:49 +0200 Subject: [PATCH 1/4] Task/improve handling of encoded data source in user settings (#7521) Improve handling of encoded data source in user settings --- apps/api/src/app/user/user.controller.ts | 10 ++++++++++ apps/api/src/app/user/user.module.ts | 4 +++- .../transform-data-source-in-response.interceptor.ts | 2 ++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/apps/api/src/app/user/user.controller.ts b/apps/api/src/app/user/user.controller.ts index 7a1fc71dc8..2b679f34cc 100644 --- a/apps/api/src/app/user/user.controller.ts +++ b/apps/api/src/app/user/user.controller.ts @@ -1,7 +1,9 @@ import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator'; import { CustomThrottlerGuard } from '@ghostfolio/api/guards/custom-throttler.guard'; import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard'; +import { decodeDataSource } from '@ghostfolio/api/helper/data-source.helper'; import { RedactValuesInResponseInterceptor } from '@ghostfolio/api/interceptors/redact-values-in-response/redact-values-in-response.interceptor'; +import { TransformDataSourceInResponseInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-response/transform-data-source-in-response.interceptor'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { ImpersonationService } from '@ghostfolio/api/services/impersonation/impersonation.service'; import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service'; @@ -119,6 +121,7 @@ export class UserController { @Get() @UseGuards(AuthGuard('jwt'), HasPermissionGuard) @UseInterceptors(RedactValuesInResponseInterceptor) + @UseInterceptors(TransformDataSourceInResponseInterceptor) public async getUser( @Headers('accept-language') acceptLanguage: string, @Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string @@ -165,6 +168,7 @@ export class UserController { @Put('setting') @UseGuards(AuthGuard('jwt'), HasPermissionGuard) + @UseInterceptors(TransformDataSourceInResponseInterceptor) public async updateUserSetting(@Body() data: UpdateUserSettingDto) { if ( size(data) === 1 && @@ -192,6 +196,12 @@ export class UserController { data ); + if (userSettings['filters.dataSource']) { + userSettings['filters.dataSource'] = decodeDataSource( + userSettings['filters.dataSource'] + ); + } + for (const key in userSettings) { if (userSettings[key] === false || userSettings[key] === null) { delete userSettings[key]; diff --git a/apps/api/src/app/user/user.module.ts b/apps/api/src/app/user/user.module.ts index 3f4e898fc5..1566309647 100644 --- a/apps/api/src/app/user/user.module.ts +++ b/apps/api/src/app/user/user.module.ts @@ -1,6 +1,7 @@ import { ActivitiesModule } from '@ghostfolio/api/app/activities/activities.module'; import { SubscriptionModule } from '@ghostfolio/api/app/subscription/subscription.module'; import { RedactValuesInResponseModule } from '@ghostfolio/api/interceptors/redact-values-in-response/redact-values-in-response.module'; +import { TransformDataSourceInResponseModule } from '@ghostfolio/api/interceptors/transform-data-source-in-response/transform-data-source-in-response.module'; import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module'; import { I18nModule } from '@ghostfolio/api/services/i18n/i18n.module'; import { ImpersonationModule } from '@ghostfolio/api/services/impersonation/impersonation.module'; @@ -30,7 +31,8 @@ import { UserService } from './user.service'; PropertyModule, RedactValuesInResponseModule, SubscriptionModule, - TagModule + TagModule, + TransformDataSourceInResponseModule ], providers: [UserService] }) 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 6b38b2d54b..81aabf4ae1 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 @@ -78,6 +78,7 @@ export class TransformDataSourceInResponseInterceptor< valueMap, object: data, paths: [ + '["filters.dataSource"]', 'activities[*].assetProfile.dataSource', 'activities[*].dataSource', 'assetProfile.dataSource', @@ -88,6 +89,7 @@ export class TransformDataSourceInResponseInterceptor< 'holdings[*].assetProfile.dataSource', 'holdings[*].dataSource', 'items[*].dataSource', + 'settings["filters.dataSource"]', 'watchlist[*].dataSource' ] }); From 75e3d032abd554a84954a7ba68d2dfc02516b69c Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Mon, 3 Aug 2026 19:56:36 +0200 Subject: [PATCH 2/4] Feature/stock splits setup (part 2) (#7523) Improve stock splits setup --- .../asset-profiles.controller.ts | 2 + .../asset-profiles/asset-profiles.module.ts | 2 + .../asset-profiles/asset-profiles.service.ts | 12 +++- .../asset-profile-dialog.html | 59 ++++++++----------- 4 files changed, 39 insertions(+), 36 deletions(-) diff --git a/apps/api/src/app/endpoints/asset-profiles/asset-profiles.controller.ts b/apps/api/src/app/endpoints/asset-profiles/asset-profiles.controller.ts index 6521665e1b..5ffb756a0d 100644 --- a/apps/api/src/app/endpoints/asset-profiles/asset-profiles.controller.ts +++ b/apps/api/src/app/endpoints/asset-profiles/asset-profiles.controller.ts @@ -142,6 +142,8 @@ export class AssetProfilesController { }); return this.assetProfilesService.createSplit({ + dataSource, + symbol, symbolProfileId, date: parseISO(data.date), denominator: data.denominator, diff --git a/apps/api/src/app/endpoints/asset-profiles/asset-profiles.module.ts b/apps/api/src/app/endpoints/asset-profiles/asset-profiles.module.ts index e3906be9ff..9df342f5f6 100644 --- a/apps/api/src/app/endpoints/asset-profiles/asset-profiles.module.ts +++ b/apps/api/src/app/endpoints/asset-profiles/asset-profiles.module.ts @@ -8,6 +8,7 @@ import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data- import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.module'; import { MarketDataModule } from '@ghostfolio/api/services/market-data/market-data.module'; import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module'; +import { DataGatheringQueueModule } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.module'; import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile/symbol-profile.module'; import { Module } from '@nestjs/common'; @@ -23,6 +24,7 @@ import { AssetProfilesService } from './asset-profiles.service'; ApiModule, AssetProfileSplitModule, BenchmarkModule, + DataGatheringQueueModule, DataProviderModule, ExchangeRateDataModule, MarketDataModule, diff --git a/apps/api/src/app/endpoints/asset-profiles/asset-profiles.service.ts b/apps/api/src/app/endpoints/asset-profiles/asset-profiles.service.ts index 7d75fc40bb..0d50e22233 100644 --- a/apps/api/src/app/endpoints/asset-profiles/asset-profiles.service.ts +++ b/apps/api/src/app/endpoints/asset-profiles/asset-profiles.service.ts @@ -5,6 +5,7 @@ import { DataProviderService } from '@ghostfolio/api/services/data-provider/data import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service'; import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service'; +import { DataGatheringService } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.service'; import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/symbol-profile.service'; import { UpdateAssetProfileDataDto } from '@ghostfolio/common/dtos'; import { @@ -33,6 +34,7 @@ export class AssetProfilesService { private readonly activitiesService: ActivitiesService, private readonly assetProfileSplitService: AssetProfileSplitService, private readonly benchmarkService: BenchmarkService, + private readonly dataGatheringService: DataGatheringService, private readonly dataProviderService: DataProviderService, private readonly exchangeRateDataService: ExchangeRateDataService, private readonly marketDataService: MarketDataService, @@ -41,22 +43,28 @@ export class AssetProfilesService { ) {} public async createSplit({ + dataSource, date, denominator, numerator, + symbol, symbolProfileId }: { date: Date; denominator: number; numerator: number; symbolProfileId: string; - }) { - return this.assetProfileSplitService.upsert({ + } & AssetProfileIdentifier) { + const assetProfileSplit = await this.assetProfileSplitService.upsert({ date, denominator, numerator, symbolProfileId }); + + await this.dataGatheringService.gatherSymbol({ dataSource, symbol }); + + return assetProfileSplit; } public async deleteSplit({ diff --git a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html index f5df34ab47..f80cec842a 100644 --- a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html +++ b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html @@ -514,10 +514,6 @@
-

- Splits are stored for this asset profile, but are not applied - to the portfolio calculation yet. -

@if (splits.length > 0) { @@ -580,38 +576,33 @@ - - Shares After - - Numerator, - e.g. 4 for a 4:1 split or 1 for a 1:10 reverse - split + - - - Shares Before - - Denominator, - e.g. 1 for a 4:1 split or 10 for a 1:10 reverse - splitShares After + + +
:
+ - + Shares Before + +
+
From 37cae1f3d812afb67d6b128cf5ca9b8f4d0c02f3 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Mon, 3 Aug 2026 20:12:13 +0200 Subject: [PATCH 4/4] Bugfix/loading indicators in user detail dialog of admin control (#7520) * Fix loading indicators * Update changelog --- CHANGELOG.md | 4 +++ .../user-detail-dialog.component.ts | 7 +++++ .../user-detail-dialog.html | 28 ++++++++++++------- 3 files changed, 29 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 38e3855f26..123ef1f948 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added the database model and endpoints to manage the stock splits of an asset profile (experimental) +### Fixed + +- Fixed the loading state in the user detail dialog of the admin control panel’s users section + ## 3.40.0 - 2026-08-02 ### Changed diff --git a/apps/client/src/app/components/user-detail-dialog/user-detail-dialog.component.ts b/apps/client/src/app/components/user-detail-dialog/user-detail-dialog.component.ts index 9e341037a4..3aec4d42c7 100644 --- a/apps/client/src/app/components/user-detail-dialog/user-detail-dialog.component.ts +++ b/apps/client/src/app/components/user-detail-dialog/user-detail-dialog.component.ts @@ -56,14 +56,18 @@ export class GfUserDetailDialogComponent implements OnInit { protected readonly baseCurrency: string; protected readonly canDeleteUser = canDeleteUser; protected readonly getCountryName = getCountryName; + protected isLoading = true; + protected readonly subscriptionsDataSource = new MatTableDataSource(); + protected readonly subscriptionsDisplayedColumns = [ 'createdAt', 'type', 'price', 'expiresAt' ]; + protected user: AdminUserResponse; protected readonly data = inject(MAT_DIALOG_DATA); @@ -72,6 +76,7 @@ export class GfUserDetailDialogComponent implements OnInit { private readonly changeDetectorRef = inject(ChangeDetectorRef); private readonly dataService = inject(DataService); private readonly destroyRef = inject(DestroyRef); + private readonly dialogRef = inject>( MatDialogRef @@ -101,6 +106,8 @@ export class GfUserDetailDialogComponent implements OnInit { this.subscriptionsDataSource.data = this.user.subscriptions ?? []; + this.isLoading = false; + this.changeDetectorRef.markForCheck(); }); } 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 e2cdb66a56..b77e0043e8 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 @@ -36,12 +36,14 @@ i18n size="medium" [enableCopyToClipboardButton]="true" - [value]="user?.id" + [value]="isLoading ? undefined : user.id" >User ID
- Role + Role
@@ -52,7 +54,7 @@ size="medium" [isDate]="true" [locale]="data.locale" - [value]="user?.createdAt" + [value]="isLoading ? undefined : user.createdAt" >Registration Date @@ -61,7 +63,7 @@ i18n size="medium" [locale]="data.locale" - [value]="user?.provider" + [value]="isLoading ? undefined : user.provider" >Authentication @@ -74,7 +76,9 @@ i18n size="medium" [locale]="data.locale" - [value]="user?.subscription ? 'Premium' : 'Basic'" + [value]=" + isLoading ? undefined : user.subscription ? 'Premium' : 'Basic' + " >Membership @@ -83,7 +87,11 @@ i18n size="medium" [value]=" - user?.country ? getCountryName({ code: user.country }) : '-' + isLoading + ? undefined + : user.country + ? getCountryName({ code: user.country }) + : '-' " >Country @@ -97,7 +105,7 @@ i18n size="medium" [locale]="data.locale" - [value]="user?.accountCount" + [value]="isLoading ? undefined : user.accountCount" >Accounts @@ -106,7 +114,7 @@ i18n size="medium" [locale]="data.locale" - [value]="user?.activityCount" + [value]="isLoading ? undefined : user.activityCount" >Activities @@ -120,7 +128,7 @@ size="medium" [locale]="data.locale" [precision]="0" - [value]="user?.engagement" + [value]="isLoading ? undefined : user.engagement" >Engagement per Day @@ -129,7 +137,7 @@ i18n size="medium" [locale]="data.locale" - [value]="user?.dailyApiRequests" + [value]="isLoading ? undefined : user.dailyApiRequests" >API Requests Today