From 11749d0c9adf829f011f831af8a2ed52ad61d17a Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Wed, 30 Sep 2026 17:14:31 +0200 Subject: [PATCH 1/6] Bugfix/sustainable retirement income for redacted values (#7976) * Fix sustainable retirement income for redacted values * Update changelog --- CHANGELOG.md | 1 + .../portfolio/fire/fire-page.component.ts | 6 ++++ .../app/pages/portfolio/fire/fire-page.html | 30 +++++++++++++++---- 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index be16617266..a31f5f7b3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed the allocation column of the top holdings of ETFs on the allocations page after a change of the user settings - Fixed the value column of the top holdings of ETFs on the allocations page in the _Presenter View_ - Fixed the projected sustainable retirement income on the _FIRE_ page for a projected total amount of zero (experimental) +- Fixed the sustainable retirement income on the _FIRE_ page for redacted values - Fixed the emergency fund in the portfolio summary for redacted values - Fixed the redaction of `valueInBaseCurrency` in the holdings of ETFs in a restricted view diff --git a/apps/client/src/app/pages/portfolio/fire/fire-page.component.ts b/apps/client/src/app/pages/portfolio/fire/fire-page.component.ts index 33b877d42f..a6a6c7dfd8 100644 --- a/apps/client/src/app/pages/portfolio/fire/fire-page.component.ts +++ b/apps/client/src/app/pages/portfolio/fire/fire-page.component.ts @@ -8,6 +8,7 @@ import { User } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; +import { hasScope, scopes } from '@ghostfolio/common/scopes'; import { GfFireCalculatorComponent } from '@ghostfolio/ui/fire-calculator'; import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator'; import { DataService } from '@ghostfolio/ui/services'; @@ -53,6 +54,7 @@ export class GfFirePageComponent implements OnInit { protected fireWealth: FireWealth; protected hasImpersonationId: boolean; + protected hasPermissionToShowValues: boolean; protected hasPermissionToUpdateUserSettings: boolean; protected isLoading = false; protected retirementDate: Date; @@ -142,6 +144,10 @@ export class GfFirePageComponent implements OnInit { if (state?.user) { this.user = state.user; + this.hasPermissionToShowValues = + hasScope(this.user.scopes, scopes.portfolioReadValues) && + !this.user.settings.isRestrictedView; + this.hasPermissionToUpdateUserSettings = this.user.subscription?.type === SubscriptionType.Basic ? false diff --git a/apps/client/src/app/pages/portfolio/fire/fire-page.html b/apps/client/src/app/pages/portfolio/fire/fire-page.html index 7315f10cb0..3f12ddc126 100644 --- a/apps/client/src/app/pages/portfolio/fire/fire-page.html +++ b/apps/client/src/app/pages/portfolio/fire/fire-page.html @@ -73,7 +73,11 @@ [isCurrency]="true" [locale]="user?.settings?.locale" [unit]="user?.settings?.baseCurrency" - [value]="withdrawalRatePerYear?.toNumber()" + [value]=" + hasPermissionToShowValues + ? withdrawalRatePerYear?.toNumber() + : null + " /> per year @@ -84,7 +88,11 @@ [isCurrency]="true" [locale]="user?.settings?.locale" [unit]="user?.settings?.baseCurrency" - [value]="withdrawalRatePerMonth?.toNumber()" + [value]=" + hasPermissionToShowValues + ? withdrawalRatePerMonth?.toNumber() + : null + " /> per month, based on your total assets of @@ -94,7 +102,11 @@ [isCurrency]="true" [locale]="user?.settings?.locale" [unit]="user?.settings?.baseCurrency" - [value]="fireWealth?.today.valueInBaseCurrency" + [value]=" + hasPermissionToShowValues + ? fireWealth?.today.valueInBaseCurrency + : null + " /> and a safe withdrawal rate (SWR) of @@ -137,7 +149,11 @@ [isCurrency]="true" [locale]="user?.settings?.locale" [unit]="user?.settings?.baseCurrency" - [value]="withdrawalRatePerYearProjected?.toNumber()" + [value]=" + hasPermissionToShowValues + ? withdrawalRatePerYearProjected?.toNumber() + : null + " /> per year @@ -148,7 +164,11 @@ [isCurrency]="true" [locale]="user?.settings?.locale" [unit]="user?.settings?.baseCurrency" - [value]="withdrawalRatePerMonthProjected?.toNumber()" + [value]=" + hasPermissionToShowValues + ? withdrawalRatePerMonthProjected?.toNumber() + : null + " /> per month, assuming a From 13a1aeab176dbc7152725de2121b180930d37a9c Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Wed, 30 Sep 2026 17:34:58 +0200 Subject: [PATCH 2/6] Bugfix/data source transformation of symbol profile in activities (#7983) * Fix data source transformation of symbol profile in activities * Update changelog --- CHANGELOG.md | 1 + .../transform-data-source-in-response.interceptor.ts | 2 ++ 2 files changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a31f5f7b3a..8b41f04751 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed the sustainable retirement income on the _FIRE_ page for redacted values - Fixed the emergency fund in the portfolio summary for redacted values - Fixed the redaction of `valueInBaseCurrency` in the holdings of ETFs in a restricted view +- Resolved the data source transformation in the asset profile relation of the activities ## 3.75.0 - 2026-09-28 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 31bfb620ed..339b91a2f7 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 @@ -83,6 +83,7 @@ export class TransformDataSourceInResponseInterceptor< '["filters.dataSource"]', 'activities[*].assetProfile.dataSource', 'activities[*].dataSource', + 'activities[*].SymbolProfile.dataSource', 'assetProfile.dataSource', 'benchmarks[*].dataSource', 'errors[*].dataSource', @@ -93,6 +94,7 @@ export class TransformDataSourceInResponseInterceptor< 'items[*].dataSource', 'latestActivities[*].assetProfile.dataSource', 'settings["filters.dataSource"]', + 'SymbolProfile.dataSource', 'watchlist[*].dataSource' ] }); From f14d2ce2eba5ca1f9227a498e57510142c2ec8e5 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Wed, 30 Sep 2026 17:35:59 +0200 Subject: [PATCH 3/6] Bugfix/redaction of symbol profile in activities (#7977) * Fix redaction of symbol profile in activities * Update changelog --- CHANGELOG.md | 1 + apps/api/src/helper/object.helper.spec.ts | 40 +++++++++++++++++++++++ libs/common/src/lib/config.ts | 6 ++++ 3 files changed, 47 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b41f04751..b2b3725a97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed the sustainable retirement income on the _FIRE_ page for redacted values - Fixed the emergency fund in the portfolio summary for redacted values - Fixed the redaction of `valueInBaseCurrency` in the holdings of ETFs in a restricted view +- Fixed the redaction of `comment`, `scraperConfiguration` and `symbolMapping` in the asset profile relation of the activities in a restricted view - Resolved the data source transformation in the asset profile relation of the activities ## 3.75.0 - 2026-09-28 diff --git a/apps/api/src/helper/object.helper.spec.ts b/apps/api/src/helper/object.helper.spec.ts index ca487ed8f7..9776232a0b 100644 --- a/apps/api/src/helper/object.helper.spec.ts +++ b/apps/api/src/helper/object.helper.spec.ts @@ -3082,6 +3082,20 @@ describe('redactAttributes', () => { feeInBaseCurrency: 18.2, id: '8c623328-6035-4b5f-b6d5-702cc1c9c56b', quantity: 50, + // The relation of the database is part of the response next to the + // asset profile + SymbolProfile: { + comment: 'Reviewed by the administrator', + name: 'Apple Inc', + scraperConfiguration: { + headers: { Authorization: 'Bearer TOKEN' }, + mode: 'lazy', + selector: '.price', + url: 'https://example.org/get_price' + }, + symbol: 'AAPL', + symbolMapping: { YAHOO: 'AAPL' } + }, type: 'BUY', unitPrice: 220.79, value: 11039.5, @@ -3111,6 +3125,13 @@ describe('redactAttributes', () => { feeInBaseCurrency: null, id: '8c623328-6035-4b5f-b6d5-702cc1c9c56b', quantity: null, + SymbolProfile: { + comment: null, + name: 'Apple Inc', + scraperConfiguration: null, + symbol: 'AAPL', + symbolMapping: null + }, type: 'BUY', // A price per unit stays visible, like the average price and the market // price of a holding @@ -3169,6 +3190,18 @@ describe('redactAttributes', () => { currency: 'USD', fee: 19.9, quantity: 50, + SymbolProfile: { + comment: 'Reviewed by the administrator', + name: 'Apple Inc', + scraperConfiguration: { + headers: { Authorization: 'Bearer TOKEN' }, + mode: 'lazy', + selector: '.price', + url: 'https://example.org/get_price' + }, + symbol: 'AAPL', + symbolMapping: { YAHOO: 'AAPL' } + }, type: 'BUY', unitPrice: 220.79, value: 11039.5, @@ -3194,6 +3227,13 @@ describe('redactAttributes', () => { currency: 'USD', fee: null, quantity: null, + SymbolProfile: { + comment: null, + name: 'Apple Inc', + scraperConfiguration: null, + symbol: 'AAPL', + symbolMapping: null + }, type: 'BUY', unitPrice: 220.79, value: null, diff --git a/libs/common/src/lib/config.ts b/libs/common/src/lib/config.ts index 6ff492f671..5a8aecff36 100644 --- a/libs/common/src/lib/config.ts +++ b/libs/common/src/lib/config.ts @@ -138,6 +138,9 @@ export const DEFAULT_REDACTED_PATHS = [ 'activities[*].feeInAssetProfileCurrency', 'activities[*].feeInBaseCurrency', 'activities[*].quantity', + 'activities[*].SymbolProfile.comment', + 'activities[*].SymbolProfile.scraperConfiguration', + 'activities[*].SymbolProfile.symbolMapping', 'activities[*].value', 'activities[*].valueInBaseCurrency', 'assetProfile.comment', @@ -180,6 +183,9 @@ export const DEFAULT_REDACTED_PATHS = [ 'settings.emergencyFund', 'settings.projectedTotalAmount', 'settings.savingsRate', + 'SymbolProfile.comment', + 'SymbolProfile.scraperConfiguration', + 'SymbolProfile.symbolMapping', 'totalBalanceInBaseCurrency', 'totalDividendInBaseCurrency', 'totalInterestInBaseCurrency', From d4e2adc6dff17ef720c52a9f81fc0fc072786d13 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Wed, 30 Sep 2026 17:36:54 +0200 Subject: [PATCH 4/6] Feature/add refresh action to user detail dialog menu in admin control (#7978) * Add refresh action * Update changelog --- CHANGELOG.md | 1 + .../user-detail-dialog.component.ts | 44 +++++++++++-------- .../user-detail-dialog.html | 4 ++ 3 files changed, 30 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b2b3725a97..a2759a3acd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Added a _Refresh_ action to the user detail dialog menu in the admin control panel - Added the total column to the table of the tag management in the admin control panel ### 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 926fb8d8cd..8d12d0ba03 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 @@ -91,25 +91,7 @@ export class GfUserDetailDialogComponent implements OnInit { } public ngOnInit() { - this.adminService - .fetchUserById(this.data.userId) - .pipe( - takeUntilDestroyed(this.destroyRef), - catchError(() => { - this.dialogRef.close(); - - return EMPTY; - }) - ) - .subscribe((user) => { - this.user = user; - - this.subscriptionsDataSource.data = this.user.subscriptions ?? []; - - this.isLoading = false; - - this.changeDetectorRef.markForCheck(); - }); + this.initialize(); } protected deleteUser() { @@ -141,6 +123,30 @@ export class GfUserDetailDialogComponent implements OnInit { : $localize`Coupon`; } + protected initialize() { + this.isLoading = true; + + this.adminService + .fetchUserById(this.data.userId) + .pipe( + takeUntilDestroyed(this.destroyRef), + catchError(() => { + this.dialogRef.close(); + + return EMPTY; + }) + ) + .subscribe((user) => { + this.user = user; + + this.subscriptionsDataSource.data = this.user.subscriptions ?? []; + + this.isLoading = false; + + this.changeDetectorRef.markForCheck(); + }); + } + protected onClose() { this.dialogRef.close(); } 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 efc4fe5b43..4cc5b8a260 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 @@ -13,6 +13,10 @@ class="no-max-width" xPosition="before" > + +