From 9eb5943666a35122ee05b4f5f299222e28c6cacf Mon Sep 17 00:00:00 2001 From: Karel De Smet Date: Tue, 27 Jan 2026 17:32:12 +0100 Subject: [PATCH 01/12] Task/improve usability when adding currency in market data section of admin control panel (#6225) * Improve usability when adding currency * Update changelog --- CHANGELOG.md | 1 + .../admin-market-data.component.ts | 39 ++++++++----------- .../create-asset-profile-dialog.component.ts | 8 +++- 3 files changed, 25 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7805ad11d..b0abeea63 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 ### Changed +- Improved the usability of the create asset profile dialog in the market data section of the admin control panel - Improved the language localization for Chinese (`zh`) - Improved the language localization for German (`de`) - Upgraded `angular` from version `21.0.6` to `21.1.1` diff --git a/apps/client/src/app/components/admin-market-data/admin-market-data.component.ts b/apps/client/src/app/components/admin-market-data/admin-market-data.component.ts index bc3b0d374..8f956b782 100644 --- a/apps/client/src/app/components/admin-market-data/admin-market-data.component.ts +++ b/apps/client/src/app/components/admin-market-data/admin-market-data.component.ts @@ -63,7 +63,7 @@ import { import { DeviceDetectorService } from 'ngx-device-detector'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; import { Subject } from 'rxjs'; -import { distinctUntilChanged, switchMap, takeUntil } from 'rxjs/operators'; +import { distinctUntilChanged, takeUntil } from 'rxjs/operators'; import { AdminMarketDataService } from './admin-market-data.service'; import { GfAssetProfileDialogComponent } from './asset-profile-dialog/asset-profile-dialog.component'; @@ -482,32 +482,27 @@ export class GfAdminMarketDataComponent dialogRef .afterClosed() .pipe(takeUntil(this.unsubscribeSubject)) - .subscribe(({ dataSource, symbol } = {}) => { - if (dataSource && symbol) { + .subscribe((result) => { + if (!result) { + this.router.navigate(['.'], { relativeTo: this.route }); + + return; + } + + const { addAssetProfile, dataSource, symbol } = result; + + if (addAssetProfile && dataSource && symbol) { this.adminService .addAssetProfile({ dataSource, symbol }) - .pipe( - switchMap(() => { - this.isLoading = true; - this.changeDetectorRef.markForCheck(); - - return this.adminService.fetchAdminMarketData({ - filters: this.activeFilters, - take: this.pageSize - }); - }), - takeUntil(this.unsubscribeSubject) - ) - .subscribe(({ marketData }) => { - this.dataSource = new MatTableDataSource(marketData); - this.dataSource.sort = this.sort; - this.isLoading = false; - - this.changeDetectorRef.markForCheck(); + .pipe(takeUntil(this.unsubscribeSubject)) + .subscribe(() => { + this.loadData(); }); + } else { + this.loadData(); } - this.router.navigate(['.'], { relativeTo: this.route }); + this.onOpenAssetProfileDialog({ dataSource, symbol }); }); }); } diff --git a/apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.component.ts b/apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.component.ts index 6c180b034..fbf8afa03 100644 --- a/apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.component.ts +++ b/apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.component.ts @@ -101,6 +101,7 @@ export class GfCreateAssetProfileDialogComponent implements OnDestroy, OnInit { public onSubmit() { if (this.mode === 'auto') { this.dialogRef.close({ + addAssetProfile: true, dataSource: this.createAssetProfileForm.get('searchSymbol').value.dataSource, symbol: this.createAssetProfileForm.get('searchSymbol').value.symbol @@ -127,10 +128,15 @@ export class GfCreateAssetProfileDialogComponent implements OnDestroy, OnInit { takeUntil(this.unsubscribeSubject) ) .subscribe(() => { - this.dialogRef.close(); + this.dialogRef.close({ + addAssetProfile: false, + dataSource: this.dataSourceForExchangeRates, + symbol: `${DEFAULT_CURRENCY}${currency}` + }); }); } else if (this.mode === 'manual') { this.dialogRef.close({ + addAssetProfile: true, dataSource: 'MANUAL', symbol: `${this.ghostfolioPrefix}${this.createAssetProfileForm.get('addSymbol').value}` }); From 5de2b02a04bb8ca02d49712802ff8e50fb674cb1 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Wed, 28 Jan 2026 21:00:51 +0100 Subject: [PATCH 02/12] Task/refactor common helper (#6251) * Refactoring --- libs/common/src/lib/helper.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/libs/common/src/lib/helper.ts b/libs/common/src/lib/helper.ts index 9ee7d6220..4db1fcf2d 100644 --- a/libs/common/src/lib/helper.ts +++ b/libs/common/src/lib/helper.ts @@ -223,8 +223,8 @@ export function getDateFormatString(aLocale?: string) { ); return formatObject - .map((object) => { - switch (object.type) { + .map(({ type, value }) => { + switch (type) { case 'day': return 'dd'; case 'month': @@ -232,7 +232,7 @@ export function getDateFormatString(aLocale?: string) { case 'year': return 'yyyy'; default: - return object.value; + return value; } }) .join(''); @@ -271,8 +271,8 @@ export function getLowercase(object: object, path: string) { export function getNumberFormatDecimal(aLocale?: string) { const formatObject = new Intl.NumberFormat(aLocale).formatToParts(9999.99); - return formatObject.find((object) => { - return object.type === 'decimal'; + return formatObject.find(({ type }) => { + return type === 'decimal'; })?.value; } @@ -281,8 +281,8 @@ export function getNumberFormatGroup(aLocale = getLocale()) { useGrouping: true }).formatToParts(9999.99); - return formatObject.find((object) => { - return object.type === 'group'; + return formatObject.find(({ type }) => { + return type === 'group'; })?.value; } From b8814855672f330a5be9634ec595ec01d597a115 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Thu, 29 Jan 2026 17:31:30 +0100 Subject: [PATCH 03/12] Task/upgrade lodash to version 4.17.23 (#6259) * Upgrade lodash to version 4.17.23 * Update changelog --- CHANGELOG.md | 1 + package-lock.json | 22 ++++++++++++++-------- package.json | 4 ++-- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b0abeea63..0abfb3e76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Improved the language localization for Chinese (`zh`) - Improved the language localization for German (`de`) - Upgraded `angular` from version `21.0.6` to `21.1.1` +- Upgraded `lodash` from version `4.17.21` to `4.17.23` - Upgraded `Nx` from version `22.3.3` to `22.4.1` - Upgraded `prettier` from version `3.8.0` to `3.8.1` diff --git a/package-lock.json b/package-lock.json index 7d6a8ee64..ebe4197a4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -68,7 +68,7 @@ "http-status-codes": "2.3.0", "ionicons": "8.0.13", "jsonpath": "1.1.1", - "lodash": "4.17.21", + "lodash": "4.17.23", "marked": "17.0.1", "ms": "3.0.0-canary.1", "ng-extract-i18n-merge": "3.2.1", @@ -125,7 +125,7 @@ "@types/google-spreadsheet": "3.1.5", "@types/jest": "30.0.0", "@types/jsonpath": "0.2.4", - "@types/lodash": "4.17.21", + "@types/lodash": "4.17.23", "@types/node": "22.15.17", "@types/papaparse": "5.3.7", "@types/passport-google-oauth20": "2.0.16", @@ -7614,6 +7614,12 @@ "url": "https://dotenvx.com" } }, + "node_modules/@nestjs/config/node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, "node_modules/@nestjs/core": { "version": "11.1.8", "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-11.1.8.tgz", @@ -13076,9 +13082,9 @@ } }, "node_modules/@types/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-FOvQ0YPD5NOfPgMzJihoT+Za5pdkDJWcbpuj1DjaKZIr/gxodQjY/uWEFlTNqW2ugXHUiL8lRQgw63dzKHZdeQ==", + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-RDvF6wTulMPjrNdCoYRC8gNR880JNGT8uB+REUpC2Ns4pRqQJhGz90wh7rgdXDPpCczF3VGktDuFGVnz8zP7HA==", "dev": true, "license": "MIT" }, @@ -25070,9 +25076,9 @@ } }, "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", "license": "MIT" }, "node_modules/lodash-es": { diff --git a/package.json b/package.json index eed936456..55087b776 100644 --- a/package.json +++ b/package.json @@ -112,7 +112,7 @@ "http-status-codes": "2.3.0", "ionicons": "8.0.13", "jsonpath": "1.1.1", - "lodash": "4.17.21", + "lodash": "4.17.23", "marked": "17.0.1", "ms": "3.0.0-canary.1", "ng-extract-i18n-merge": "3.2.1", @@ -169,7 +169,7 @@ "@types/google-spreadsheet": "3.1.5", "@types/jest": "30.0.0", "@types/jsonpath": "0.2.4", - "@types/lodash": "4.17.21", + "@types/lodash": "4.17.23", "@types/node": "22.15.17", "@types/papaparse": "5.3.7", "@types/passport-google-oauth20": "2.0.16", From ad2abbc55ba5ba009c4b17a3d55f552c8f61ab3e Mon Sep 17 00:00:00 2001 From: Lyndros <38167685+Lyndros@users.noreply.github.com> Date: Fri, 30 Jan 2026 13:36:57 +0100 Subject: [PATCH 04/12] Task/improve Spanish translation (#6261) * Improve Spanish translation * Update changelog --- CHANGELOG.md | 1 + apps/client/src/locales/messages.es.xlf | 180 ++++++++++++------------ 2 files changed, 91 insertions(+), 90 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0abfb3e76..cfb65404c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Improved the usability of the create asset profile dialog in the market data section of the admin control panel - Improved the language localization for Chinese (`zh`) - Improved the language localization for German (`de`) +- Improved the language localization for Spanish (`es`) - Upgraded `angular` from version `21.0.6` to `21.1.1` - Upgraded `lodash` from version `4.17.21` to `4.17.23` - Upgraded `Nx` from version `22.3.3` to `22.4.1` diff --git a/apps/client/src/locales/messages.es.xlf b/apps/client/src/locales/messages.es.xlf index 121e9e949..7564e4d80 100644 --- a/apps/client/src/locales/messages.es.xlf +++ b/apps/client/src/locales/messages.es.xlf @@ -40,7 +40,7 @@ please - please + por favor apps/client/src/app/pages/pricing/pricing-page.html 333 @@ -84,7 +84,7 @@ with - with + con apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html 87 @@ -368,7 +368,7 @@ and is driven by the efforts of its contributors - and is driven by the efforts of its contributors + y es impulsado por los esfuerzos de sus contribuidores apps/client/src/app/pages/about/overview/about-overview-page.html 49 @@ -652,7 +652,7 @@ No auto-renewal on membership. - No auto-renewal on membership. + No se renueva automáticamente la membresía. apps/client/src/app/components/user-account-membership/user-account-membership.html 74 @@ -1096,7 +1096,7 @@ Performance with currency effect - Performance with currency effect + Rendimiento con el efecto del tipo de cambio de divisa apps/client/src/app/pages/portfolio/analysis/analysis-page.html 135 @@ -1912,7 +1912,7 @@ Current week - Current week + Semana actual apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts 191 @@ -2076,7 +2076,7 @@ or start a discussion at - or start a discussion at + o iniciar una discusión en apps/client/src/app/pages/about/overview/about-overview-page.html 94 @@ -2148,7 +2148,7 @@ Sustainable retirement income - Sustainable retirement income + Ingreso sostenible de retiro apps/client/src/app/pages/portfolio/fire/fire-page.html 41 @@ -2320,7 +2320,7 @@ contact us - contact us + contactarnos apps/client/src/app/pages/pricing/pricing-page.html 336 @@ -2420,7 +2420,7 @@ Latest activities - Latest activities + Últimas actividades apps/client/src/app/pages/public/public-page.html 211 @@ -2536,7 +2536,7 @@ annual interest rate - annual interest rate + tasa de interés anual apps/client/src/app/pages/portfolio/fire/fire-page.html 185 @@ -2656,7 +2656,7 @@ Could not validate form - Could not validate form + No se pudo validar el formulario apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts 554 @@ -2892,7 +2892,7 @@ Authentication - Authentication + Autenticación apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 35 @@ -3044,7 +3044,7 @@ If you retire today, you would be able to withdraw - If you retire today, you would be able to withdraw + Si te retirases hoy, podrías sacar apps/client/src/app/pages/portfolio/fire/fire-page.html 68 @@ -3112,7 +3112,7 @@ Looking for a student discount? - Looking for a student discount? + ¿Buscando un descuento para estudiantes? apps/client/src/app/pages/pricing/pricing-page.html 342 @@ -3348,7 +3348,7 @@ Everything in Basic, plus - Everything in Basic, plus + Todo en Básico, más apps/client/src/app/pages/pricing/pricing-page.html 199 @@ -3608,7 +3608,7 @@ Could not save asset profile - Could not save asset profile + No se pudo guardar el perfil del activo apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts 588 @@ -3812,7 +3812,7 @@ By - By + Por apps/client/src/app/pages/portfolio/fire/fire-page.html 139 @@ -3828,7 +3828,7 @@ Current year - Current year + Año actual apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts 199 @@ -3864,7 +3864,7 @@ Asset profile has been saved - Asset profile has been saved + El perfil del activo ha sido guardado apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts 578 @@ -4056,7 +4056,7 @@ View Details - View Details + Ver detalles apps/client/src/app/components/admin-users/admin-users.html 225 @@ -4192,7 +4192,7 @@ per week - per week + por semana apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html 130 @@ -4216,7 +4216,7 @@ and we share aggregated key metrics of the platform’s performance - and we share aggregated key metrics of the platform’s performance + y compartimos agregados métricas clave del rendimiento de la plataforma apps/client/src/app/pages/about/overview/about-overview-page.html 32 @@ -4260,7 +4260,7 @@ Website of Thomas Kaul - Website of Thomas Kaul + Sitio web de Thomas Kaul apps/client/src/app/pages/about/overview/about-overview-page.html 44 @@ -4440,7 +4440,7 @@ Sign in with OpenID Connect - Sign in with OpenID Connect + Iniciar sesión con OpenID Connect apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html 55 @@ -4532,7 +4532,7 @@ The source code is fully available as open source software (OSS) under the AGPL-3.0 license - The source code is fully available as open source software (OSS) under the AGPL-3.0 license + El código fuente está disponible completamente en software de código abierto (OSS) bajo la licencia AGPL-3.0 apps/client/src/app/pages/about/overview/about-overview-page.html 16 @@ -4604,7 +4604,7 @@ this is projected to increase to - this is projected to increase to + esto se proyecta a aumentar a apps/client/src/app/pages/portfolio/fire/fire-page.html 147 @@ -4656,7 +4656,7 @@ Job ID - Job ID + ID de trabajo apps/client/src/app/components/admin-jobs/admin-jobs.html 34 @@ -4740,7 +4740,7 @@ for - for + para apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html 128 @@ -4764,7 +4764,7 @@ Could not parse scraper configuration - Could not parse scraper configuration + No se pudo analizar la configuración del scraper apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts 509 @@ -4808,7 +4808,7 @@ Edit access - Edit access + Editar acceso apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html 11 @@ -4880,7 +4880,7 @@ Get access to 80’000+ tickers from over 50 exchanges - Get access to 80’000+ tickers from over 50 exchanges + Obtén acceso a más de 80,000 tickers de más de 50 exchanges apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html 84 @@ -5064,7 +5064,7 @@ less than - less than + menos que apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html 129 @@ -5354,7 +5354,7 @@ Ghostfolio Status - Ghostfolio Status + Estado de Ghostfolio apps/client/src/app/pages/about/overview/about-overview-page.html 62 @@ -5362,7 +5362,7 @@ with your university e-mail address - with your university e-mail address + con tu dirección de correo electrónico de la universidad apps/client/src/app/pages/pricing/pricing-page.html 348 @@ -5382,7 +5382,7 @@ and a safe withdrawal rate (SWR) of - and a safe withdrawal rate (SWR) of + y una tasa de retiro segura (SWR) de apps/client/src/app/pages/portfolio/fire/fire-page.html 108 @@ -5546,7 +5546,7 @@ Request it - Request it + Solicitar apps/client/src/app/pages/pricing/pricing-page.html 344 @@ -5602,7 +5602,7 @@ , - , + , apps/client/src/app/pages/portfolio/fire/fire-page.html 145 @@ -5618,7 +5618,7 @@ per month - per month + por mes apps/client/src/app/pages/portfolio/fire/fire-page.html 94 @@ -5866,7 +5866,7 @@ here - here + aquí apps/client/src/app/pages/pricing/pricing-page.html 347 @@ -5874,7 +5874,7 @@ Close Holding - Close Holding + Cerrar posición apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html 442 @@ -6175,7 +6175,7 @@ {VAR_PLURAL, plural, =1 {activity} other {activities}} - {VAR_PLURAL, plural, =1 {activity} other {activities}} + {VAR_PLURAL, plural, =1 {actividad} other {actividades}} apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html 14 @@ -6255,7 +6255,7 @@ Include in - Include in + Incluir en apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html 374 @@ -6539,7 +6539,7 @@ View Holding - View Holding + Ver fondos libs/ui/src/lib/activities-table/activities-table.component.html 450 @@ -6683,7 +6683,7 @@ Oops! Could not update access. - Oops! Could not update access. + Oops! No se pudo actualizar el acceso. apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.component.ts 178 @@ -6691,7 +6691,7 @@ , based on your total assets of - , based on your total assets of + , basado en tus activos totales de apps/client/src/app/pages/portfolio/fire/fire-page.html 96 @@ -6763,7 +6763,7 @@ Close - Cerca + Cerrar apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html 594 @@ -6807,7 +6807,7 @@ Role - Role + Rol apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 14 @@ -6839,7 +6839,7 @@ Change with currency effect Change - Change with currency effect Change + Cambiar con efecto de cambio dedivisa Cambiar apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html 63 @@ -6847,7 +6847,7 @@ If you plan to open an account at - If you plan to open an account at + Si planeas abrir una cuenta en apps/client/src/app/pages/pricing/pricing-page.html 312 @@ -6855,7 +6855,7 @@ Performance with currency effect Performance - Performance with currency effect Performance + Rendimiento con cambio de divisa Rendimiento apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html 83 @@ -6879,7 +6879,7 @@ send an e-mail to - send an e-mail to + enviar un correo electrónico a apps/client/src/app/pages/about/overview/about-overview-page.html 87 @@ -6951,7 +6951,7 @@ , assuming a - , assuming a + , asumiendo un apps/client/src/app/pages/portfolio/fire/fire-page.html 174 @@ -6959,7 +6959,7 @@ to use our referral link and get a Ghostfolio Premium membership for one year - to use our referral link and get a Ghostfolio Premium membership for one year + para usar nuestro enlace de referido y obtener una membresía Ghostfolio Premium por un año apps/client/src/app/pages/pricing/pricing-page.html 340 @@ -7039,7 +7039,7 @@ Ghostfolio is a lightweight wealth management application for individuals to keep track of stocks, ETFs or cryptocurrencies and make solid, data-driven investment decisions. - Ghostfolio is a lightweight wealth management application for individuals to keep track of stocks, ETFs or cryptocurrencies and make solid, data-driven investment decisions. + Ghostfolio es una aplicación de gestión de patrimonio para aquellos individuos que desean realizar un seguimiento de acciones, ETFs o criptomonedas y tomar decisiones de inversión sólidas y basadas en datos. apps/client/src/app/pages/about/overview/about-overview-page.html 10 @@ -7353,7 +7353,7 @@ Check the system status at - Check the system status at + Verificar el estado del sistema en apps/client/src/app/pages/about/overview/about-overview-page.html 57 @@ -7369,7 +7369,7 @@ Change with currency effect - Change with currency effect + Cambiar con el efecto del tipo de cambio de divisa apps/client/src/app/pages/portfolio/analysis/analysis-page.html 116 @@ -7509,7 +7509,7 @@ The project has been initiated by - The project has been initiated by + El proyecto ha sido iniciado por apps/client/src/app/pages/about/overview/about-overview-page.html 40 @@ -7533,7 +7533,7 @@ Total amount - Total amount + Cantidad total apps/client/src/app/pages/portfolio/analysis/analysis-page.html 95 @@ -7625,7 +7625,7 @@ Find account, holding or page... - Find account, holding or page... + Buscar cuenta, posición o página... libs/ui/src/lib/assistant/assistant.component.ts 151 @@ -8049,7 +8049,7 @@ Current month - Current month + Mes actual apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts 195 @@ -8234,7 +8234,7 @@ If you encounter a bug, would like to suggest an improvement or a new feature, please join the Ghostfolio Slack community, post to @ghostfolio_ - If you encounter a bug, would like to suggest an improvement or a new feature, please join the Ghostfolio Slack community, post to @ghostfolio_ + Si encuentras un error, deseas sugerir una mejora o una nueva característica, por favor únete a la comunidad Ghostfolio Slack, publica en @ghostfolio_ apps/client/src/app/pages/about/overview/about-overview-page.html 69 @@ -8266,7 +8266,7 @@ - + apps/client/src/app/components/admin-users/admin-users.html 39 @@ -8334,7 +8334,7 @@ Economic Market Cluster Risks - Economic Market Cluster Risks + Riesgos del clúster de mercados económicos apps/client/src/app/pages/i18n/i18n-page.html 106 @@ -8342,7 +8342,7 @@ Emergency Fund - Emergency Fund + Fondo de emergencia apps/client/src/app/pages/i18n/i18n-page.html 144 @@ -8350,7 +8350,7 @@ Fees - Fees + Comisiones apps/client/src/app/pages/i18n/i18n-page.html 161 @@ -8358,7 +8358,7 @@ Liquidity - Liquidity + Liquidez apps/client/src/app/pages/i18n/i18n-page.html 70 @@ -8366,7 +8366,7 @@ Buying Power - Buying Power + Poder de compra apps/client/src/app/pages/i18n/i18n-page.html 71 @@ -8374,7 +8374,7 @@ Your buying power is below ${thresholdMin} ${baseCurrency} - Your buying power is below ${thresholdMin} ${baseCurrency} + Tu poder de compra es inferior a ${thresholdMin} ${baseCurrency} apps/client/src/app/pages/i18n/i18n-page.html 73 @@ -8382,7 +8382,7 @@ Your buying power is 0 ${baseCurrency} - Your buying power is 0 ${baseCurrency} + Tu poder de compra es 0 ${baseCurrency} apps/client/src/app/pages/i18n/i18n-page.html 77 @@ -8390,7 +8390,7 @@ Your buying power exceeds ${thresholdMin} ${baseCurrency} - Your buying power exceeds ${thresholdMin} ${baseCurrency} + Tu poder de compra excede ${thresholdMin} ${baseCurrency} apps/client/src/app/pages/i18n/i18n-page.html 80 @@ -8422,7 +8422,7 @@ The developed markets contribution of your current investment (${developedMarketsValueRatio}%) exceeds ${thresholdMax}% - The developed markets contribution of your current investment (${developedMarketsValueRatio}%) exceeds ${thresholdMax}% + La contribución a los mercados desarrollados de tu inversión actual (${developedMarketsValueRatio}%) supera el ${thresholdMax}% apps/client/src/app/pages/i18n/i18n-page.html 112 @@ -8430,7 +8430,7 @@ The developed markets contribution of your current investment (${developedMarketsValueRatio}%) is below ${thresholdMin}% - The developed markets contribution of your current investment (${developedMarketsValueRatio}%) is below ${thresholdMin}% + La contribución a los mercados desarrollados de tu inversión actual (${developedMarketsValueRatio}%) es inferior al ${thresholdMin}% apps/client/src/app/pages/i18n/i18n-page.html 117 @@ -8438,7 +8438,7 @@ The developed markets contribution of your current investment (${developedMarketsValueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% - The developed markets contribution of your current investment (${developedMarketsValueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% + La contribución a los mercados desarrollados de tu inversión actual (${developedMarketsValueRatio}%) está dentro del rango de ${thresholdMin}% y ${thresholdMax}% apps/client/src/app/pages/i18n/i18n-page.html 122 @@ -8454,7 +8454,7 @@ The emerging markets contribution of your current investment (${emergingMarketsValueRatio}%) exceeds ${thresholdMax}% - The emerging markets contribution of your current investment (${emergingMarketsValueRatio}%) exceeds ${thresholdMax}% + La contribución a los mercados emergentes de tu inversión actual (${emergingMarketsValueRatio}%) supera el ${thresholdMax}% apps/client/src/app/pages/i18n/i18n-page.html 130 @@ -8462,7 +8462,7 @@ The emerging markets contribution of your current investment (${emergingMarketsValueRatio}%) is below ${thresholdMin}% - The emerging markets contribution of your current investment (${emergingMarketsValueRatio}%) is below ${thresholdMin}% + La contribución a los mercados emergentes de tu inversión actual (${emergingMarketsValueRatio}%) es inferior al ${thresholdMin}% apps/client/src/app/pages/i18n/i18n-page.html 135 @@ -8470,7 +8470,7 @@ The emerging markets contribution of your current investment (${emergingMarketsValueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% - The emerging markets contribution of your current investment (${emergingMarketsValueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% + La contribución a los mercados emergentes de tu inversión actual (${emergingMarketsValueRatio}%) está dentro del rango de ${thresholdMin}% y ${thresholdMax}% apps/client/src/app/pages/i18n/i18n-page.html 140 @@ -8494,7 +8494,7 @@ Asia-Pacific - Asia-Pacific + Asia-Pacífico apps/client/src/app/pages/i18n/i18n-page.html 165 @@ -8502,7 +8502,7 @@ The Asia-Pacific market contribution of your current investment (${valueRatio}%) exceeds ${thresholdMax}% - The Asia-Pacific market contribution of your current investment (${valueRatio}%) exceeds ${thresholdMax}% + La contribución al mercado de Asia-Pacífico de tu inversión actual (${valueRatio}%) supera el ${thresholdMax}% apps/client/src/app/pages/i18n/i18n-page.html 167 @@ -8510,7 +8510,7 @@ The Asia-Pacific market contribution of your current investment (${valueRatio}%) is below ${thresholdMin}% - The Asia-Pacific market contribution of your current investment (${valueRatio}%) is below ${thresholdMin}% + La contribución al mercado de Asia-Pacífico de tu inversión actual (${valueRatio}%) es inferior al ${thresholdMin}% apps/client/src/app/pages/i18n/i18n-page.html 171 @@ -8518,7 +8518,7 @@ The Asia-Pacific market contribution of your current investment (${valueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% - The Asia-Pacific market contribution of your current investment (${valueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% + La contribución al mercado de Asia-Pacífico de tu inversión actual (${valueRatio}%) está dentro del rango de ${thresholdMin}% y ${thresholdMax}% apps/client/src/app/pages/i18n/i18n-page.html 175 @@ -8526,7 +8526,7 @@ Emerging Markets - Emerging Markets + Mercados emergentes apps/client/src/app/pages/i18n/i18n-page.html 180 @@ -8534,7 +8534,7 @@ The Emerging Markets contribution of your current investment (${valueRatio}%) exceeds ${thresholdMax}% - The Emerging Markets contribution of your current investment (${valueRatio}%) exceeds ${thresholdMax}% + La contribución a los mercados emergentes de tu inversión actual (${valueRatio}%) supera el ${thresholdMax}% apps/client/src/app/pages/i18n/i18n-page.html 183 @@ -8542,7 +8542,7 @@ The Emerging Markets contribution of your current investment (${valueRatio}%) is below ${thresholdMin}% - The Emerging Markets contribution of your current investment (${valueRatio}%) is below ${thresholdMin}% + La contribución a los mercados emergentes de tu inversión actual (${valueRatio}%) es inferior al ${thresholdMin}% apps/client/src/app/pages/i18n/i18n-page.html 187 @@ -8550,7 +8550,7 @@ The Emerging Markets contribution of your current investment (${valueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% - The Emerging Markets contribution of your current investment (${valueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% + La contribución a los mercados emergentes de tu inversión actual (${valueRatio}%) está dentro del rango de ${thresholdMin}% y ${thresholdMax}% apps/client/src/app/pages/i18n/i18n-page.html 191 @@ -8558,7 +8558,7 @@ Europe - Europe + Europa apps/client/src/app/pages/i18n/i18n-page.html 195 @@ -8566,7 +8566,7 @@ The Europe market contribution of your current investment (${valueRatio}%) exceeds ${thresholdMax}% - The Europe market contribution of your current investment (${valueRatio}%) exceeds ${thresholdMax}% + La contribución al mercado europeo de tu inversión actual (${valueRatio}%) supera el ${thresholdMax}% apps/client/src/app/pages/i18n/i18n-page.html 197 @@ -8574,7 +8574,7 @@ The Europe market contribution of your current investment (${valueRatio}%) is below ${thresholdMin}% - The Europe market contribution of your current investment (${valueRatio}%) is below ${thresholdMin}% + La contribución al mercado europeo de tu inversión actual (${valueRatio}%) es inferior al ${thresholdMin}% apps/client/src/app/pages/i18n/i18n-page.html 201 @@ -8582,7 +8582,7 @@ The Europe market contribution of your current investment (${valueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% - The Europe market contribution of your current investment (${valueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% + La contribución al mercado europeo de tu inversión actual (${valueRatio}%) está dentro del rango de ${thresholdMin}% y ${thresholdMax}% apps/client/src/app/pages/i18n/i18n-page.html 205 @@ -8694,7 +8694,7 @@ Registration Date - Registration Date + Fecha de registro apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 26 From 9a153076cabfc6a27895a605f26e73ce663aa188 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Fri, 30 Jan 2026 19:58:43 +0100 Subject: [PATCH 05/12] Release 2.234.0 (#6262) --- CHANGELOG.md | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cfb65404c..f65bbd458 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ 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 +## 2.234.0 - 2026-01-30 ### Changed diff --git a/package-lock.json b/package-lock.json index ebe4197a4..b8d343edd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ghostfolio", - "version": "2.233.0", + "version": "2.234.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ghostfolio", - "version": "2.233.0", + "version": "2.234.0", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/package.json b/package.json index 55087b776..44df5228a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ghostfolio", - "version": "2.233.0", + "version": "2.234.0", "homepage": "https://ghostfol.io", "license": "AGPL-3.0", "repository": "https://github.com/ghostfolio/ghostfolio", From 418e8bc843f1aa9e1584d122371f62e527de8708 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 31 Jan 2026 08:32:33 +0100 Subject: [PATCH 06/12] Task/remove deprecated first buy date in portfolio calculator (#6244) * Remove deprecated firstBuyDate * Update changelog --- CHANGELOG.md | 6 +++++ .../calculator/portfolio-calculator.ts | 3 --- ...tfolio-calculator-baln-buy-and-buy.spec.ts | 1 - ...aln-buy-and-sell-in-two-activities.spec.ts | 1 - ...folio-calculator-baln-buy-and-sell.spec.ts | 1 - .../portfolio-calculator-baln-buy.spec.ts | 1 - .../roai/portfolio-calculator-btceur.spec.ts | 1 - ...ator-btcusd-buy-and-sell-partially.spec.ts | 1 - .../roai/portfolio-calculator-btcusd.spec.ts | 1 - .../roai/portfolio-calculator-cash.spec.ts | 1 - .../portfolio-calculator-googl-buy.spec.ts | 1 - ...-calculator-msft-buy-with-dividend.spec.ts | 1 - ...ulator-novn-buy-and-sell-partially.spec.ts | 1 - ...folio-calculator-novn-buy-and-sell.spec.ts | 1 - .../portfolio-calculator-valuable.spec.ts | 1 - .../transaction-point-symbol.interface.ts | 4 ---- .../src/app/portfolio/portfolio.service.ts | 22 ++++++++++++------- .../src/lib/models/timeline-position.ts | 3 --- 18 files changed, 20 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f65bbd458..d1092b049 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 + +### Changed + +- Removed the deprecated `firstBuyDate` in the portfolio calculator + ## 2.234.0 - 2026-01-30 ### Changed diff --git a/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts b/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts index b3b1d3410..9612ad1c4 100644 --- a/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts +++ b/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts @@ -416,7 +416,6 @@ export abstract class PortfolioCalculator { dividendInBaseCurrency: totalDividendInBaseCurrency, fee: item.fee, feeInBaseCurrency: item.feeInBaseCurrency, - firstBuyDate: item.firstBuyDate, grossPerformance: !hasErrors ? (grossPerformance ?? null) : null, grossPerformancePercentage: !hasErrors ? (grossPerformancePercentage ?? null) @@ -1004,7 +1003,6 @@ export abstract class PortfolioCalculator { fee: oldAccumulatedSymbol.fee.plus(fee), feeInBaseCurrency: oldAccumulatedSymbol.feeInBaseCurrency.plus(feeInBaseCurrency), - firstBuyDate: oldAccumulatedSymbol.firstBuyDate, includeInHoldings: oldAccumulatedSymbol.includeInHoldings, quantity: newQuantity, tags: oldAccumulatedSymbol.tags.concat(tags), @@ -1024,7 +1022,6 @@ export abstract class PortfolioCalculator { averagePrice: unitPrice, dateOfFirstActivity: date, dividend: new Big(0), - firstBuyDate: date, includeInHoldings: INVESTMENT_ACTIVITY_TYPES.includes(type), investment: unitPrice.mul(quantity).mul(factor), quantity: quantity.mul(factor), diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-buy.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-buy.spec.ts index a1021a57b..7858d7546 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-buy.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-buy.spec.ts @@ -153,7 +153,6 @@ describe('PortfolioCalculator', () => { dividendInBaseCurrency: new Big('0'), fee: new Big('3.2'), feeInBaseCurrency: new Big('3.2'), - firstBuyDate: '2021-11-22', grossPerformance: new Big('36.6'), grossPerformancePercentage: new Big('0.07706261539956593567'), grossPerformancePercentageWithCurrencyEffect: new Big( diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell-in-two-activities.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell-in-two-activities.spec.ts index 002730e32..8b40c7b70 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell-in-two-activities.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell-in-two-activities.spec.ts @@ -169,7 +169,6 @@ describe('PortfolioCalculator', () => { dividendInBaseCurrency: new Big('0'), fee: new Big('3.2'), feeInBaseCurrency: new Big('3.2'), - firstBuyDate: '2021-11-22', grossPerformance: new Big('-12.6'), grossPerformancePercentage: new Big('-0.04408677396780965649'), grossPerformancePercentageWithCurrencyEffect: new Big( diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell.spec.ts index e4ba70158..fc372f68f 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell.spec.ts @@ -153,7 +153,6 @@ describe('PortfolioCalculator', () => { dividendInBaseCurrency: new Big('0'), fee: new Big('3.2'), feeInBaseCurrency: new Big('3.2'), - firstBuyDate: '2021-11-22', grossPerformance: new Big('-12.6'), grossPerformancePercentage: new Big('-0.0440867739678096571'), grossPerformancePercentageWithCurrencyEffect: new Big( diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy.spec.ts index e6cae7865..926fae6dc 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy.spec.ts @@ -143,7 +143,6 @@ describe('PortfolioCalculator', () => { dividendInBaseCurrency: new Big('0'), fee: new Big('1.55'), feeInBaseCurrency: new Big('1.55'), - firstBuyDate: '2021-11-30', grossPerformance: new Big('24.6'), grossPerformancePercentage: new Big('0.09004392386530014641'), grossPerformancePercentageWithCurrencyEffect: new Big( diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur.spec.ts index 6cc58a70f..b216438b8 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur.spec.ts @@ -204,7 +204,6 @@ describe('PortfolioCalculator', () => { dividendInBaseCurrency: new Big('0'), fee: new Big('4.46'), feeInBaseCurrency: new Big('4.46'), - firstBuyDate: '2021-12-12', grossPerformance: new Big('-1458.72'), grossPerformancePercentage: new Big('-0.03273724696701543726'), grossPerformancePercentageWithCurrencyEffect: new Big( diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts index 41f1d80a8..14cd4f217 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts @@ -167,7 +167,6 @@ describe('PortfolioCalculator', () => { dividendInBaseCurrency: new Big('0'), fee: new Big('0'), feeInBaseCurrency: new Big('0'), - firstBuyDate: '2015-01-01', grossPerformance: new Big('27172.74').mul(0.97373), grossPerformancePercentage: new Big('0.4241983590271396608571'), grossPerformancePercentageWithCurrencyEffect: new Big( diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd.spec.ts index b8cecb350..066f33ea3 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd.spec.ts @@ -204,7 +204,6 @@ describe('PortfolioCalculator', () => { dividendInBaseCurrency: new Big('0'), fee: new Big('4.46'), feeInBaseCurrency: new Big('4.46'), - firstBuyDate: '2021-12-12', grossPerformance: new Big('-1458.72'), grossPerformancePercentage: new Big('-0.03273724696701543726'), grossPerformancePercentageWithCurrencyEffect: new Big( diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-cash.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-cash.spec.ts index bbcaba294..bd8afddd7 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-cash.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-cash.spec.ts @@ -239,7 +239,6 @@ describe('PortfolioCalculator', () => { dividendInBaseCurrency: new Big(0), fee: new Big(0), feeInBaseCurrency: new Big(0), - firstBuyDate: '2023-12-31', grossPerformance: new Big(0), grossPerformancePercentage: new Big(0), grossPerformancePercentageWithCurrencyEffect: new Big( diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-googl-buy.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-googl-buy.spec.ts index e438d9c6d..28b44e159 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-googl-buy.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-googl-buy.spec.ts @@ -149,7 +149,6 @@ describe('PortfolioCalculator', () => { dividendInBaseCurrency: new Big('0'), fee: new Big('1'), feeInBaseCurrency: new Big('0.9238'), - firstBuyDate: '2023-01-03', grossPerformance: new Big('27.33').mul(0.8854), grossPerformancePercentage: new Big('0.3066651705565529623'), grossPerformancePercentageWithCurrencyEffect: new Big( diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-msft-buy-with-dividend.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-msft-buy-with-dividend.spec.ts index 88895b8c6..87ef9ed8b 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-msft-buy-with-dividend.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-msft-buy-with-dividend.spec.ts @@ -139,7 +139,6 @@ describe('PortfolioCalculator', () => { dividend: new Big('0.62'), dividendInBaseCurrency: new Big('0.62'), fee: new Big('19'), - firstBuyDate: '2021-09-16', grossPerformance: new Big('33.25'), grossPerformancePercentage: new Big('0.11136043941322258691'), grossPerformancePercentageWithCurrencyEffect: new Big( diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell-partially.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell-partially.spec.ts index 8c0b1af6a..7a8dc010a 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell-partially.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell-partially.spec.ts @@ -149,7 +149,6 @@ describe('PortfolioCalculator', () => { dividendInBaseCurrency: new Big('0'), fee: new Big('4.25'), feeInBaseCurrency: new Big('4.25'), - firstBuyDate: '2022-03-07', grossPerformance: new Big('21.93'), grossPerformancePercentage: new Big('0.15113417083448194384'), grossPerformancePercentageWithCurrencyEffect: new Big( diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell.spec.ts index c4850db66..02a4e80d8 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell.spec.ts @@ -202,7 +202,6 @@ describe('PortfolioCalculator', () => { dividendInBaseCurrency: new Big('0'), fee: new Big('0'), feeInBaseCurrency: new Big('0'), - firstBuyDate: '2022-03-07', grossPerformance: new Big('19.86'), grossPerformancePercentage: new Big('0.13100263852242744063'), grossPerformancePercentageWithCurrencyEffect: new Big( diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-valuable.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-valuable.spec.ts index 5e73841ce..610a52c06 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-valuable.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-valuable.spec.ts @@ -125,7 +125,6 @@ describe('PortfolioCalculator', () => { dividendInBaseCurrency: new Big('0'), fee: new Big('0'), feeInBaseCurrency: new Big('0'), - firstBuyDate: '2022-01-01', grossPerformance: new Big('0'), grossPerformancePercentage: new Big('0'), grossPerformancePercentageWithCurrencyEffect: new Big('0'), diff --git a/apps/api/src/app/portfolio/interfaces/transaction-point-symbol.interface.ts b/apps/api/src/app/portfolio/interfaces/transaction-point-symbol.interface.ts index ab2351f11..7e7d741ea 100644 --- a/apps/api/src/app/portfolio/interfaces/transaction-point-symbol.interface.ts +++ b/apps/api/src/app/portfolio/interfaces/transaction-point-symbol.interface.ts @@ -11,10 +11,6 @@ export interface TransactionPointSymbol { dividend: Big; fee: Big; feeInBaseCurrency: Big; - - /** @deprecated use dateOfFirstActivity instead */ - firstBuyDate: string; - includeInHoldings: boolean; investment: Big; quantity: Big; diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts index 7db743a43..05df6a8fc 100644 --- a/apps/api/src/app/portfolio/portfolio.service.ts +++ b/apps/api/src/app/portfolio/portfolio.service.ts @@ -576,8 +576,8 @@ export class PortfolioService { for (const { activitiesCount, currency, + dateOfFirstActivity, dividend, - firstBuyDate, grossPerformance, grossPerformanceWithCurrencyEffect, grossPerformancePercentage, @@ -633,7 +633,7 @@ export class PortfolioService { assetSubClass: assetProfile.assetSubClass, countries: assetProfile.countries, dataSource: assetProfile.dataSource, - dateOfFirstActivity: parseDate(firstBuyDate), + dateOfFirstActivity: parseDate(dateOfFirstActivity), dividend: dividend?.toNumber() ?? 0, grossPerformance: grossPerformance?.toNumber() ?? 0, grossPerformancePercent: grossPerformancePercentage?.toNumber() ?? 0, @@ -801,9 +801,9 @@ export class PortfolioService { activitiesCount, averagePrice, currency, + dateOfFirstActivity, dividendInBaseCurrency, feeInBaseCurrency, - firstBuyDate, grossPerformance, grossPerformancePercentage, grossPerformancePercentageWithCurrencyEffect, @@ -828,7 +828,10 @@ export class PortfolioService { }); const dividendYieldPercent = getAnnualizedPerformancePercent({ - daysInMarket: differenceInDays(new Date(), parseDate(firstBuyDate)), + daysInMarket: differenceInDays( + new Date(), + parseDate(dateOfFirstActivity) + ), netPerformancePercentage: timeWeightedInvestment.eq(0) ? new Big(0) : dividendInBaseCurrency.div(timeWeightedInvestment) @@ -836,7 +839,10 @@ export class PortfolioService { const dividendYieldPercentWithCurrencyEffect = getAnnualizedPerformancePercent({ - daysInMarket: differenceInDays(new Date(), parseDate(firstBuyDate)), + daysInMarket: differenceInDays( + new Date(), + parseDate(dateOfFirstActivity) + ), netPerformancePercentage: timeWeightedInvestmentWithCurrencyEffect.eq(0) ? new Big(0) : dividendInBaseCurrency.div(timeWeightedInvestmentWithCurrencyEffect) @@ -845,7 +851,7 @@ export class PortfolioService { const historicalData = await this.dataProviderService.getHistorical( [{ dataSource, symbol }], 'day', - parseISO(firstBuyDate), + parseISO(dateOfFirstActivity), new Date() ); @@ -910,7 +916,7 @@ export class PortfolioService { // Add historical entry for buy date, if no historical data available historicalDataArray.push({ averagePrice: activitiesOfHolding[0].unitPriceInAssetProfileCurrency, - date: firstBuyDate, + date: dateOfFirstActivity, marketPrice: activitiesOfHolding[0].unitPriceInAssetProfileCurrency, quantity: activitiesOfHolding[0].quantity }); @@ -924,6 +930,7 @@ export class PortfolioService { return { activitiesCount, + dateOfFirstActivity, marketPrice, marketPriceMax, marketPriceMin, @@ -931,7 +938,6 @@ export class PortfolioService { tags, averagePrice: averagePrice.toNumber(), dataProviderInfo: portfolioCalculator.getDataProviderInfos()?.[0], - dateOfFirstActivity: firstBuyDate, dividendInBaseCurrency: dividendInBaseCurrency.toNumber(), dividendYieldPercent: dividendYieldPercent.toNumber(), dividendYieldPercentWithCurrencyEffect: diff --git a/libs/common/src/lib/models/timeline-position.ts b/libs/common/src/lib/models/timeline-position.ts index 244d6595e..9cfb2df04 100644 --- a/libs/common/src/lib/models/timeline-position.ts +++ b/libs/common/src/lib/models/timeline-position.ts @@ -35,9 +35,6 @@ export class TimelinePosition { @Type(() => Big) feeInBaseCurrency: Big; - /** @deprecated use dateOfFirstActivity instead */ - firstBuyDate: string; - @Transform(transformToBig, { toClassOnly: true }) @Type(() => Big) grossPerformance: Big; From 7dbacdbf0fa835f0093bd70907dbdc75b7132522 Mon Sep 17 00:00:00 2001 From: Kenrick Tandrian <60643640+KenTandrian@users.noreply.github.com> Date: Sat, 31 Jan 2026 14:43:04 +0700 Subject: [PATCH 07/12] Feature/extract top holdings from Yahoo Finance for ETF and mutual funds (#6254) * Extract top holdings from Yahoo Finance for ETF and mutual funds * Update changelog --- CHANGELOG.md | 4 ++++ .../yahoo-finance/yahoo-finance.service.ts | 17 +++++++++++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d1092b049..a3fb00a66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Added + +- Added the ability to fetch top holdings for ETF and mutual fund assets from _Yahoo Finance_ + ### Changed - Removed the deprecated `firstBuyDate` in the portfolio calculator diff --git a/apps/api/src/services/data-provider/data-enhancer/yahoo-finance/yahoo-finance.service.ts b/apps/api/src/services/data-provider/data-enhancer/yahoo-finance/yahoo-finance.service.ts index 65bcd6c06..97c875360 100644 --- a/apps/api/src/services/data-provider/data-enhancer/yahoo-finance/yahoo-finance.service.ts +++ b/apps/api/src/services/data-provider/data-enhancer/yahoo-finance/yahoo-finance.service.ts @@ -135,10 +135,10 @@ export class YahooFinanceDataEnhancerService implements DataEnhancerInterface { shortName, symbol }: { - longName: Price['longName']; - quoteType: Price['quoteType']; - shortName: Price['shortName']; - symbol: Price['symbol']; + longName?: Price['longName']; + quoteType?: Price['quoteType']; + shortName?: Price['shortName']; + symbol?: Price['symbol']; }) { let name = longName; @@ -217,6 +217,15 @@ export class YahooFinanceDataEnhancerService implements DataEnhancerInterface { }); } } + + response.holdings = assetProfile.topHoldings.holdings.map( + ({ holdingName, holdingPercent }) => { + return { + name: this.formatName({ longName: holdingName }), + weight: holdingPercent + }; + } + ); } else if ( assetSubClass === 'STOCK' && assetProfile.summaryProfile?.country From eae2c20dfefb48c11e3affc15e8119f7b4534c98 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 31 Jan 2026 11:48:51 +0100 Subject: [PATCH 08/12] Task/deprecate transactionCount in GET api/v1/admin endpoint (#6265) * Deprecate transactionCount in favor of activitiesCount * Update changelog --- CHANGELOG.md | 1 + apps/api/src/app/admin/admin.service.ts | 7 ++++--- .../components/admin-overview/admin-overview.component.ts | 6 +++--- .../src/app/components/admin-overview/admin-overview.html | 6 +++--- libs/common/src/lib/interfaces/admin-data.interface.ts | 4 ++++ 5 files changed, 15 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a3fb00a66..39634dfc4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Deprecated `transactionCount` in favor of `activitiesCount` in the endpoint `GET api/v1/admin` - Removed the deprecated `firstBuyDate` in the portfolio calculator ## 2.234.0 - 2026-01-30 diff --git a/apps/api/src/app/admin/admin.service.ts b/apps/api/src/app/admin/admin.service.ts index 705085a48..cd18eb239 100644 --- a/apps/api/src/app/admin/admin.service.ts +++ b/apps/api/src/app/admin/admin.service.ts @@ -138,11 +138,11 @@ export class AdminService { public async get(): Promise { const dataSources = Object.values(DataSource); - const [enabledDataSources, settings, transactionCount, userCount] = + const [activitiesCount, enabledDataSources, settings, userCount] = await Promise.all([ + this.prismaService.order.count(), this.dataProviderService.getDataSources(), this.propertyService.get(), - this.prismaService.order.count(), this.countUsersWithAnalytics() ]); @@ -182,10 +182,11 @@ export class AdminService { ).filter(Boolean); return { + activitiesCount, dataProviders, settings, - transactionCount, userCount, + transactionCount: activitiesCount, version: environment.version }; } diff --git a/apps/client/src/app/components/admin-overview/admin-overview.component.ts b/apps/client/src/app/components/admin-overview/admin-overview.component.ts index 6284f05fd..c0ccb0f64 100644 --- a/apps/client/src/app/components/admin-overview/admin-overview.component.ts +++ b/apps/client/src/app/components/admin-overview/admin-overview.component.ts @@ -73,6 +73,7 @@ import { takeUntil } from 'rxjs/operators'; templateUrl: './admin-overview.html' }) export class GfAdminOverviewComponent implements OnDestroy, OnInit { + public activitiesCount: number; public couponDuration: StringValue = '14 days'; public coupons: Coupon[]; public hasPermissionForSubscription: boolean; @@ -83,7 +84,6 @@ export class GfAdminOverviewComponent implements OnDestroy, OnInit { public isDataGatheringEnabled: boolean; public permissions = permissions; public systemMessage: SystemMessage; - public transactionCount: number; public userCount: number; public user: User; public version: string; @@ -289,12 +289,12 @@ export class GfAdminOverviewComponent implements OnDestroy, OnInit { this.adminService .fetchAdminData() .pipe(takeUntil(this.unsubscribeSubject)) - .subscribe(({ settings, transactionCount, userCount, version }) => { + .subscribe(({ activitiesCount, settings, userCount, version }) => { + this.activitiesCount = activitiesCount; this.coupons = (settings[PROPERTY_COUPONS] as Coupon[]) ?? []; this.isDataGatheringEnabled = settings[PROPERTY_IS_DATA_GATHERING_ENABLED] === false ? false : true; this.systemMessage = settings[PROPERTY_SYSTEM_MESSAGE] as SystemMessage; - this.transactionCount = transactionCount; this.userCount = userCount; this.version = version; diff --git a/apps/client/src/app/components/admin-overview/admin-overview.html b/apps/client/src/app/components/admin-overview/admin-overview.html index c47387f37..f0a6ea1d5 100644 --- a/apps/client/src/app/components/admin-overview/admin-overview.html +++ b/apps/client/src/app/components/admin-overview/admin-overview.html @@ -20,11 +20,11 @@
- @if (transactionCount && userCount) { + @if (activitiesCount && userCount) {
- {{ transactionCount / userCount | number: '1.2-2' }} + {{ activitiesCount / userCount | number: '1.2-2' }} per User
} diff --git a/libs/common/src/lib/interfaces/admin-data.interface.ts b/libs/common/src/lib/interfaces/admin-data.interface.ts index 23821a86b..63588300c 100644 --- a/libs/common/src/lib/interfaces/admin-data.interface.ts +++ b/libs/common/src/lib/interfaces/admin-data.interface.ts @@ -1,12 +1,16 @@ import { DataProviderInfo } from './data-provider-info.interface'; export interface AdminData { + activitiesCount: number; dataProviders: (DataProviderInfo & { assetProfileCount: number; useForExchangeRates: boolean; })[]; settings: { [key: string]: boolean | object | string | string[] }; + + /** @deprecated use activitiesCount instead */ transactionCount: number; + userCount: number; version: string; } From 2520d0d9619672b0125de4c49cf1b1ba4400bfd1 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Mon, 2 Feb 2026 20:44:04 +0100 Subject: [PATCH 09/12] Task/upgrade yahoo-finance2 to version 3.13.0 (#6263) * Upgrade yahoo-finance2 to version 3.13.0 * Update changelog --- CHANGELOG.md | 1 + package-lock.json | 8 ++++---- package.json | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 39634dfc4..371a8e6f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Deprecated `transactionCount` in favor of `activitiesCount` in the endpoint `GET api/v1/admin` - Removed the deprecated `firstBuyDate` in the portfolio calculator +- Upgraded `yahoo-finance2` from version `3.11.2` to `3.13.0` ## 2.234.0 - 2026-01-30 diff --git a/package-lock.json b/package-lock.json index b8d343edd..38789fd53 100644 --- a/package-lock.json +++ b/package-lock.json @@ -88,7 +88,7 @@ "svgmap": "2.14.0", "tablemark": "4.1.0", "twitter-api-v2": "1.27.0", - "yahoo-finance2": "3.11.2", + "yahoo-finance2": "3.13.0", "zone.js": "0.16.0" }, "devDependencies": { @@ -35361,9 +35361,9 @@ } }, "node_modules/yahoo-finance2": { - "version": "3.11.2", - "resolved": "https://registry.npmjs.org/yahoo-finance2/-/yahoo-finance2-3.11.2.tgz", - "integrity": "sha512-SIvMXjrOktBRD8m+qXAGCK+vR1vwBKuMgCnvmbxv29+t6LTDu0vAUxNYfbigsMRTmBzS4F9TQwbYF90g3Om4HA==", + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/yahoo-finance2/-/yahoo-finance2-3.13.0.tgz", + "integrity": "sha512-czBj2q/MD68YEsB7aXNnGhJvWxYZn01O5r/i7VYiQV2m2sWwhca6tKgjwf/LT7zHHEVxhKNiGLB46glLnmq9Ag==", "license": "MIT", "dependencies": { "@deno/shim-deno": "~0.18.0", diff --git a/package.json b/package.json index 44df5228a..9dc3a9914 100644 --- a/package.json +++ b/package.json @@ -132,7 +132,7 @@ "svgmap": "2.14.0", "tablemark": "4.1.0", "twitter-api-v2": "1.27.0", - "yahoo-finance2": "3.11.2", + "yahoo-finance2": "3.13.0", "zone.js": "0.16.0" }, "devDependencies": { From 9c4638c185d434ef765f2257f2b2c482dc8dd56a Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Tue, 3 Feb 2026 19:30:37 +0100 Subject: [PATCH 10/12] Feature/add impersonation mode in get account balances endpoint (#6272) * Add impersonation mode * Update changelog --- CHANGELOG.md | 1 + apps/api/src/app/account/account.controller.ts | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 371a8e6f4..f3bdf396b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Added the ability to fetch top holdings for ETF and mutual fund assets from _Yahoo Finance_ +- Added support for the impersonation mode in the endpoint `GET api/v1/account/:id/balances` ### Changed diff --git a/apps/api/src/app/account/account.controller.ts b/apps/api/src/app/account/account.controller.ts index 542b199fd..052720176 100644 --- a/apps/api/src/app/account/account.controller.ts +++ b/apps/api/src/app/account/account.controller.ts @@ -132,12 +132,16 @@ export class AccountController { @UseGuards(AuthGuard('jwt'), HasPermissionGuard) @UseInterceptors(RedactValuesInResponseInterceptor) public async getAccountBalancesById( + @Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string, @Param('id') id: string ): Promise { + const impersonationUserId = + await this.impersonationService.validateImpersonationId(impersonationId); + return this.accountBalanceService.getAccountBalances({ filters: [{ id, type: 'ACCOUNT' }], userCurrency: this.request.user.settings.settings.baseCurrency, - userId: this.request.user.id + userId: impersonationUserId || this.request.user.id }); } From 5b6d6a419fa308a83b218e9c7cf6ec7526bcb8d9 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Tue, 3 Feb 2026 19:37:51 +0100 Subject: [PATCH 11/12] Task/refactor transactions in accounts table component (#6266) * Refactor showTransactions and transactionCount * Update changelog --- CHANGELOG.md | 2 ++ .../holding-detail-dialog.html | 2 +- .../app/pages/accounts/accounts-page.component.ts | 10 +++++----- .../src/app/pages/accounts/accounts-page.html | 2 +- .../accounts-table/accounts-table.component.html | 4 ++-- .../accounts-table.component.stories.ts | 14 +++++++------- .../lib/accounts-table/accounts-table.component.ts | 8 ++++---- 7 files changed, 22 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f3bdf396b..df8851c66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Refactored `showTransactions` in favor of `showActivitiesCount` in the accounts table component +- Refactored `transactionCount` in favor of `activitiesCount` in the accounts table component - Deprecated `transactionCount` in favor of `activitiesCount` in the endpoint `GET api/v1/admin` - Removed the deprecated `firstBuyDate` in the portfolio calculator - Upgraded `yahoo-finance2` from version `3.11.2` to `3.13.0` diff --git a/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html index f9329dbfb..27df91a17 100644 --- a/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html +++ b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -380,10 +380,10 @@ [deviceType]="data.deviceType" [hasPermissionToOpenDetails]="false" [locale]="user?.settings?.locale" + [showActivitiesCount]="false" [showAllocationInPercentage]="user?.settings?.isExperimentalFeatures" [showBalance]="false" [showFooter]="false" - [showTransactions]="false" [showValue]="false" [showValueInBaseCurrency]="false" /> diff --git a/apps/client/src/app/pages/accounts/accounts-page.component.ts b/apps/client/src/app/pages/accounts/accounts-page.component.ts index 6c8146f77..f7e6541b5 100644 --- a/apps/client/src/app/pages/accounts/accounts-page.component.ts +++ b/apps/client/src/app/pages/accounts/accounts-page.component.ts @@ -38,6 +38,7 @@ import { GfTransferBalanceDialogComponent } from './transfer-balance/transfer-ba }) export class GfAccountsPageComponent implements OnDestroy, OnInit { public accounts: AccountModel[]; + public activitiesCount = 0; public deviceType: string; public hasImpersonationId: boolean; public hasPermissionToCreateAccount: boolean; @@ -45,7 +46,6 @@ export class GfAccountsPageComponent implements OnDestroy, OnInit { public routeQueryParams: Subscription; public totalBalanceInBaseCurrency = 0; public totalValueInBaseCurrency = 0; - public transactionCount = 0; public user: User; private unsubscribeSubject = new Subject(); @@ -128,14 +128,14 @@ export class GfAccountsPageComponent implements OnDestroy, OnInit { .subscribe( ({ accounts, + activitiesCount, totalBalanceInBaseCurrency, - totalValueInBaseCurrency, - transactionCount + totalValueInBaseCurrency }) => { this.accounts = accounts; + this.activitiesCount = activitiesCount; this.totalBalanceInBaseCurrency = totalBalanceInBaseCurrency; this.totalValueInBaseCurrency = totalValueInBaseCurrency; - this.transactionCount = transactionCount; if (this.accounts?.length <= 0) { this.router.navigate([], { queryParams: { createDialog: true } }); @@ -358,8 +358,8 @@ export class GfAccountsPageComponent implements OnDestroy, OnInit { private reset() { this.accounts = undefined; + this.activitiesCount = 0; this.totalBalanceInBaseCurrency = 0; this.totalValueInBaseCurrency = 0; - this.transactionCount = 0; } } diff --git a/apps/client/src/app/pages/accounts/accounts-page.html b/apps/client/src/app/pages/accounts/accounts-page.html index 6f29a4f7c..0c6b7b8f3 100644 --- a/apps/client/src/app/pages/accounts/accounts-page.html +++ b/apps/client/src/app/pages/accounts/accounts-page.html @@ -4,6 +4,7 @@

Accounts

- + - {{ transactionCount }} + {{ activitiesCount }} diff --git a/libs/ui/src/lib/accounts-table/accounts-table.component.stories.ts b/libs/ui/src/lib/accounts-table/accounts-table.component.stories.ts index 53c59a95f..96da4419d 100644 --- a/libs/ui/src/lib/accounts-table/accounts-table.component.stories.ts +++ b/libs/ui/src/lib/accounts-table/accounts-table.component.stories.ts @@ -115,10 +115,10 @@ export const Loading: Story = { hasPermissionToOpenDetails: false, locale: 'en-US', showActions: false, + showActivitiesCount: true, showAllocationInPercentage: false, showBalance: true, showFooter: true, - showTransactions: true, showValue: true, showValueInBaseCurrency: true } @@ -127,39 +127,39 @@ export const Loading: Story = { export const Default: Story = { args: { accounts, + activitiesCount: 12, baseCurrency: 'USD', deviceType: 'desktop', hasPermissionToOpenDetails: false, locale: 'en-US', showActions: false, + showActivitiesCount: true, showAllocationInPercentage: false, showBalance: true, showFooter: true, - showTransactions: true, showValue: true, showValueInBaseCurrency: true, totalBalanceInBaseCurrency: 12428.2, - totalValueInBaseCurrency: 107971.70321466809, - transactionCount: 12 + totalValueInBaseCurrency: 107971.70321466809 } }; export const WithoutFooter: Story = { args: { accounts, + activitiesCount: 12, baseCurrency: 'USD', deviceType: 'desktop', hasPermissionToOpenDetails: false, locale: 'en-US', showActions: false, + showActivitiesCount: true, showAllocationInPercentage: false, showBalance: true, showFooter: false, - showTransactions: true, showValue: true, showValueInBaseCurrency: true, totalBalanceInBaseCurrency: 12428.2, - totalValueInBaseCurrency: 107971.70321466809, - transactionCount: 12 + totalValueInBaseCurrency: 107971.70321466809 } }; diff --git a/libs/ui/src/lib/accounts-table/accounts-table.component.ts b/libs/ui/src/lib/accounts-table/accounts-table.component.ts index fe91e1eda..21300fdcc 100644 --- a/libs/ui/src/lib/accounts-table/accounts-table.component.ts +++ b/libs/ui/src/lib/accounts-table/accounts-table.component.ts @@ -55,20 +55,20 @@ import { Subject, Subscription } from 'rxjs'; }) export class GfAccountsTableComponent implements OnChanges, OnDestroy { @Input() accounts: Account[]; + @Input() activitiesCount: number; @Input() baseCurrency: string; @Input() deviceType: string; @Input() hasPermissionToOpenDetails = true; @Input() locale = getLocale(); @Input() showActions: boolean; + @Input() showActivitiesCount = true; @Input() showAllocationInPercentage: boolean; @Input() showBalance = true; @Input() showFooter = true; - @Input() showTransactions = true; @Input() showValue = true; @Input() showValueInBaseCurrency = true; @Input() totalBalanceInBaseCurrency: number; @Input() totalValueInBaseCurrency: number; - @Input() transactionCount: number; @Output() accountDeleted = new EventEmitter(); @Output() accountToUpdate = new EventEmitter(); @@ -101,8 +101,8 @@ export class GfAccountsTableComponent implements OnChanges, OnDestroy { public ngOnChanges() { this.displayedColumns = ['status', 'account', 'platform']; - if (this.showTransactions) { - this.displayedColumns.push('transactions'); + if (this.showActivitiesCount) { + this.displayedColumns.push('activitiesCount'); } if (this.showBalance) { From 0034ed0adf9f31b1b830827852ba31a431483374 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Tue, 3 Feb 2026 19:55:18 +0100 Subject: [PATCH 12/12] Feature/introduce fast-redact in value redaction interceptor (#6269) * Introduce fast-redact * Update changelog --------- Co-authored-by: Valentin Zickner --- CHANGELOG.md | 1 + .../src/app/portfolio/portfolio.controller.ts | 2 + apps/api/src/helper/object.helper.spec.ts | 91 ++++++------------- apps/api/src/helper/object.helper.ts | 69 ++++---------- .../redact-values-in-response.interceptor.ts | 42 ++------- ...orm-data-source-in-response.interceptor.ts | 19 ++-- libs/common/src/lib/config.ts | 52 +++++++++++ package-lock.json | 18 ++++ package.json | 2 + 9 files changed, 142 insertions(+), 154 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index df8851c66..0a565308b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Optimized the value redaction interceptor for the impersonation mode by introducing `fast-redact` - Refactored `showTransactions` in favor of `showActivitiesCount` in the accounts table component - Refactored `transactionCount` in favor of `activitiesCount` in the accounts table component - Deprecated `transactionCount` in favor of `activitiesCount` in the endpoint `GET api/v1/admin` diff --git a/apps/api/src/app/portfolio/portfolio.controller.ts b/apps/api/src/app/portfolio/portfolio.controller.ts index a5a1d95ee..b8aefe0ac 100644 --- a/apps/api/src/app/portfolio/portfolio.controller.ts +++ b/apps/api/src/app/portfolio/portfolio.controller.ts @@ -195,11 +195,13 @@ export class PortfolioController { 'excludedAccountsAndActivities', 'fees', 'filteredValueInBaseCurrency', + 'fireWealth', 'grossPerformance', 'grossPerformanceWithCurrencyEffect', 'interestInBaseCurrency', 'items', 'liabilities', + 'liabilitiesInBaseCurrency', 'netPerformance', 'netPerformanceWithCurrencyEffect', 'totalBuy', diff --git a/apps/api/src/helper/object.helper.spec.ts b/apps/api/src/helper/object.helper.spec.ts index e1ec81b8f..5ddff164b 100644 --- a/apps/api/src/helper/object.helper.spec.ts +++ b/apps/api/src/helper/object.helper.spec.ts @@ -1,4 +1,6 @@ -import { query, redactAttributes } from './object.helper'; +import { DEFAULT_REDACTED_PATHS } from '@ghostfolio/common/config'; + +import { query, redactPaths } from './object.helper'; describe('query', () => { it('should get market price from stock API response', () => { @@ -22,46 +24,38 @@ describe('query', () => { describe('redactAttributes', () => { it('should redact provided attributes', () => { - expect(redactAttributes({ object: {}, options: [] })).toStrictEqual({}); + expect(redactPaths({ object: {}, paths: [] })).toStrictEqual({}); - expect( - redactAttributes({ object: { value: 1000 }, options: [] }) - ).toStrictEqual({ value: 1000 }); + expect(redactPaths({ object: { value: 1000 }, paths: [] })).toStrictEqual({ + value: 1000 + }); expect( - redactAttributes({ + redactPaths({ object: { value: 1000 }, - options: [{ attribute: 'value', valueMap: { '*': null } }] + paths: ['value'] }) ).toStrictEqual({ value: null }); expect( - redactAttributes({ + redactPaths({ object: { value: 'abc' }, - options: [{ attribute: 'value', valueMap: { abc: 'xyz' } }] + paths: ['value'], + valueMap: { abc: 'xyz' } }) ).toStrictEqual({ value: 'xyz' }); expect( - redactAttributes({ + redactPaths({ object: { data: [{ value: 'a' }, { value: 'b' }] }, - options: [{ attribute: 'value', valueMap: { a: 1, b: 2 } }] + paths: ['data[*].value'], + valueMap: { a: 1, b: 2 } }) ).toStrictEqual({ data: [{ value: 1 }, { value: 2 }] }); - expect( - redactAttributes({ - object: { value1: 'a', value2: 'b' }, - options: [ - { attribute: 'value1', valueMap: { a: 'x' } }, - { attribute: 'value2', valueMap: { '*': 'y' } } - ] - }) - ).toStrictEqual({ value1: 'x', value2: 'y' }); - console.time('redactAttributes execution time'); expect( - redactAttributes({ + redactPaths({ object: { accounts: { '2e937c05-657c-4de9-8fb3-0813a2245f26': { @@ -1564,34 +1558,7 @@ describe('redactAttributes', () => { currentNetWorth: null } }, - options: [ - 'balance', - 'balanceInBaseCurrency', - 'comment', - 'convertedBalance', - 'dividendInBaseCurrency', - 'fee', - 'feeInBaseCurrency', - 'grossPerformance', - 'grossPerformanceWithCurrencyEffect', - 'investment', - 'netPerformance', - 'netPerformanceWithCurrencyEffect', - 'quantity', - 'symbolMapping', - 'totalBalanceInBaseCurrency', - 'totalValueInBaseCurrency', - 'unitPrice', - 'value', - 'valueInBaseCurrency' - ].map((attribute) => { - return { - attribute, - valueMap: { - '*': null - } - }; - }) + paths: DEFAULT_REDACTED_PATHS }) ).toStrictEqual({ accounts: { @@ -1681,7 +1648,7 @@ describe('redactAttributes', () => { ], dataSource: 'EOD_HISTORICAL_DATA', dateOfFirstActivity: '2021-11-30T23:00:00.000Z', - dividend: 0, + dividend: null, grossPerformance: null, grossPerformancePercent: 0.3183066634822068, grossPerformancePercentWithCurrencyEffect: 0.3183066634822068, @@ -1728,7 +1695,7 @@ describe('redactAttributes', () => { ], dataSource: 'YAHOO', dateOfFirstActivity: '2021-04-22T22:00:00.000Z', - dividend: 192, + dividend: null, grossPerformance: null, grossPerformancePercent: 0.3719230057375532, grossPerformancePercentWithCurrencyEffect: 0.2650716044872953, @@ -1780,7 +1747,7 @@ describe('redactAttributes', () => { ], dataSource: 'YAHOO', dateOfFirstActivity: '2018-09-30T22:00:00.000Z', - dividend: 0, + dividend: null, grossPerformance: null, grossPerformancePercent: 0.8594552890963852, grossPerformancePercentWithCurrencyEffect: 0.8594552890963852, @@ -1831,7 +1798,7 @@ describe('redactAttributes', () => { countries: [], dataSource: 'COINGECKO', dateOfFirstActivity: '2017-08-15T22:00:00.000Z', - dividend: 0, + dividend: null, grossPerformance: null, grossPerformancePercent: 17.4925166352, grossPerformancePercentWithCurrencyEffect: 17.4925166352, @@ -1882,7 +1849,7 @@ describe('redactAttributes', () => { countries: [], dataSource: 'MANUAL', dateOfFirstActivity: '2021-01-31T23:00:00.000Z', - dividend: 11.45, + dividend: null, grossPerformance: null, grossPerformancePercent: 0, grossPerformancePercentWithCurrencyEffect: -0.06153834320225245, @@ -1986,7 +1953,7 @@ describe('redactAttributes', () => { ], dataSource: 'MANUAL', dateOfFirstActivity: '2021-03-31T22:00:00.000Z', - dividend: 0, + dividend: null, grossPerformance: null, grossPerformancePercent: 0.27579517683678895, grossPerformancePercentWithCurrencyEffect: 0.458553421589667, @@ -2038,7 +2005,7 @@ describe('redactAttributes', () => { ], dataSource: 'YAHOO', dateOfFirstActivity: '2023-01-02T23:00:00.000Z', - dividend: 0, + dividend: null, grossPerformance: null, grossPerformancePercent: 0.7865431171216295, grossPerformancePercentWithCurrencyEffect: 0.7865431171216295, @@ -2090,7 +2057,7 @@ describe('redactAttributes', () => { ], dataSource: 'YAHOO', dateOfFirstActivity: '2017-01-02T23:00:00.000Z', - dividend: 0, + dividend: null, grossPerformance: null, grossPerformancePercent: 17.184314638161936, grossPerformancePercentWithCurrencyEffect: 17.184314638161936, @@ -2172,7 +2139,7 @@ describe('redactAttributes', () => { ], dataSource: 'YAHOO', dateOfFirstActivity: '2019-02-28T23:00:00.000Z', - dividend: 0, + dividend: null, grossPerformance: null, grossPerformancePercent: 0.8832083851170418, grossPerformancePercentWithCurrencyEffect: 0.8832083851170418, @@ -2567,7 +2534,7 @@ describe('redactAttributes', () => { ], dataSource: 'YAHOO', dateOfFirstActivity: '2018-02-28T23:00:00.000Z', - dividend: 0, + dividend: null, grossPerformance: null, grossPerformancePercent: 0.3683200415015591, grossPerformancePercentWithCurrencyEffect: 0.5806366182968891, @@ -2846,7 +2813,7 @@ describe('redactAttributes', () => { ], dataSource: 'YAHOO', dateOfFirstActivity: '2021-08-18T22:00:00.000Z', - dividend: 0, + dividend: null, grossPerformance: null, grossPerformancePercent: 0.3474381850624522, grossPerformancePercentWithCurrencyEffect: 0.28744846894552306, @@ -2964,7 +2931,7 @@ describe('redactAttributes', () => { assetClass: 'LIQUIDITY', assetSubClass: 'CASH', countries: [], - dividend: 0, + dividend: null, grossPerformance: null, grossPerformancePercent: 0, grossPerformancePercentWithCurrencyEffect: 0, diff --git a/apps/api/src/helper/object.helper.ts b/apps/api/src/helper/object.helper.ts index 6bb6579d2..350d5fe04 100644 --- a/apps/api/src/helper/object.helper.ts +++ b/apps/api/src/helper/object.helper.ts @@ -1,6 +1,6 @@ -import { Big } from 'big.js'; +import fastRedact from 'fast-redact'; import jsonpath from 'jsonpath'; -import { cloneDeep, isArray, isObject } from 'lodash'; +import { cloneDeep, isObject } from 'lodash'; export function hasNotDefinedValuesInObject(aObject: Object): boolean { for (const key in aObject) { @@ -42,60 +42,29 @@ export function query({ return jsonpath.query(object, pathExpression); } -export function redactAttributes({ - isFirstRun = true, +export function redactPaths({ object, - options + paths, + valueMap }: { - isFirstRun?: boolean; object: any; - options: { attribute: string; valueMap: { [key: string]: any } }[]; + paths: fastRedact.RedactOptions['paths']; + valueMap?: { [key: string]: any }; }): any { - if (!object || !options?.length) { - return object; - } - - // Create deep clone - const redactedObject = isFirstRun - ? JSON.parse(JSON.stringify(object)) - : object; - - for (const option of options) { - if (redactedObject.hasOwnProperty(option.attribute)) { - if (option.valueMap['*'] || option.valueMap['*'] === null) { - redactedObject[option.attribute] = option.valueMap['*']; - } else if (option.valueMap[redactedObject[option.attribute]]) { - redactedObject[option.attribute] = - option.valueMap[redactedObject[option.attribute]]; - } - } else { - // If the attribute is not present on the current object, - // check if it exists on any nested objects - for (const property in redactedObject) { - if (isArray(redactedObject[property])) { - redactedObject[property] = redactedObject[property].map( - (currentObject) => { - return redactAttributes({ - options, - isFirstRun: false, - object: currentObject - }); - } - ); - } else if ( - isObject(redactedObject[property]) && - !(redactedObject[property] instanceof Big) - ) { - // Recursively call the function on the nested object - redactedObject[property] = redactAttributes({ - options, - isFirstRun: false, - object: redactedObject[property] - }); + const redact = fastRedact({ + paths, + censor: (value) => { + if (valueMap) { + if (valueMap[value]) { + return valueMap[value]; + } else { + return value; } + } else { + return null; } } - } + }); - return redactedObject; + return JSON.parse(redact(object)); } diff --git a/apps/api/src/interceptors/redact-values-in-response/redact-values-in-response.interceptor.ts b/apps/api/src/interceptors/redact-values-in-response/redact-values-in-response.interceptor.ts index 5ecf7c48d..60b994cac 100644 --- a/apps/api/src/interceptors/redact-values-in-response/redact-values-in-response.interceptor.ts +++ b/apps/api/src/interceptors/redact-values-in-response/redact-values-in-response.interceptor.ts @@ -1,5 +1,8 @@ -import { redactAttributes } from '@ghostfolio/api/helper/object.helper'; -import { HEADER_KEY_IMPERSONATION } from '@ghostfolio/common/config'; +import { redactPaths } from '@ghostfolio/api/helper/object.helper'; +import { + DEFAULT_REDACTED_PATHS, + HEADER_KEY_IMPERSONATION +} from '@ghostfolio/common/config'; import { hasReadRestrictedAccessPermission, isRestrictedView @@ -39,40 +42,9 @@ export class RedactValuesInResponseInterceptor implements NestInterceptor< }) || isRestrictedView(user) ) { - data = redactAttributes({ + data = redactPaths({ object: data, - options: [ - 'balance', - 'balanceInBaseCurrency', - 'comment', - 'convertedBalance', - 'dividendInBaseCurrency', - 'fee', - 'feeInBaseCurrency', - 'grossPerformance', - 'grossPerformanceWithCurrencyEffect', - 'interestInBaseCurrency', - 'investment', - 'netPerformance', - 'netPerformanceWithCurrencyEffect', - 'quantity', - 'symbolMapping', - 'totalBalanceInBaseCurrency', - 'totalDividendInBaseCurrency', - 'totalInterestInBaseCurrency', - 'totalValueInBaseCurrency', - 'unitPrice', - 'unitPriceInAssetProfileCurrency', - 'value', - 'valueInBaseCurrency' - ].map((attribute) => { - return { - attribute, - valueMap: { - '*': null - } - }; - }) + paths: DEFAULT_REDACTED_PATHS }); } 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 9af256671..eaa6dd08c 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 @@ -1,4 +1,4 @@ -import { redactAttributes } from '@ghostfolio/api/helper/object.helper'; +import { redactPaths } from '@ghostfolio/api/helper/object.helper'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { encodeDataSource } from '@ghostfolio/common/helper'; @@ -58,13 +58,18 @@ export class TransformDataSourceInResponseInterceptor< } } - data = redactAttributes({ + data = redactPaths({ + valueMap, object: data, - options: [ - { - valueMap, - attribute: 'dataSource' - } + paths: [ + 'activities[*].SymbolProfile.dataSource', + 'benchmarks[*].dataSource', + 'fearAndGreedIndex.CRYPTOCURRENCIES.dataSource', + 'fearAndGreedIndex.STOCKS.dataSource', + 'holdings[*].dataSource', + 'items[*].dataSource', + 'SymbolProfile.dataSource', + 'watchlist[*].dataSource' ] }); } diff --git a/libs/common/src/lib/config.ts b/libs/common/src/lib/config.ts index a10a828e1..b558ccc42 100644 --- a/libs/common/src/lib/config.ts +++ b/libs/common/src/lib/config.ts @@ -78,6 +78,58 @@ export const DEFAULT_PROCESSOR_GATHER_HISTORICAL_MARKET_DATA_CONCURRENCY = 1; export const DEFAULT_PROCESSOR_PORTFOLIO_SNAPSHOT_COMPUTATION_CONCURRENCY = 1; export const DEFAULT_PROCESSOR_PORTFOLIO_SNAPSHOT_COMPUTATION_TIMEOUT = 30000; +export const DEFAULT_REDACTED_PATHS = [ + 'accounts[*].balance', + 'accounts[*].valueInBaseCurrency', + 'activities[*].account.balance', + 'activities[*].account.comment', + 'activities[*].comment', + 'activities[*].fee', + 'activities[*].feeInAssetProfileCurrency', + 'activities[*].feeInBaseCurrency', + 'activities[*].quantity', + 'activities[*].SymbolProfile.symbolMapping', + 'activities[*].SymbolProfile.watchedByCount', + 'activities[*].value', + 'activities[*].valueInBaseCurrency', + 'balance', + 'balanceInBaseCurrency', + 'balances[*].account.balance', + 'balances[*].account.comment', + 'balances[*].value', + 'balances[*].valueInBaseCurrency', + 'comment', + 'dividendInBaseCurrency', + 'feeInBaseCurrency', + 'grossPerformance', + 'grossPerformanceWithCurrencyEffect', + 'historicalData[*].quantity', + 'holdings[*].dividend', + 'holdings[*].grossPerformance', + 'holdings[*].grossPerformanceWithCurrencyEffect', + 'holdings[*].holdings[*].valueInBaseCurrency', + 'holdings[*].investment', + 'holdings[*].netPerformance', + 'holdings[*].netPerformanceWithCurrencyEffect', + 'holdings[*].quantity', + 'holdings[*].valueInBaseCurrency', + 'interestInBaseCurrency', + 'investmentInBaseCurrencyWithCurrencyEffect', + 'netPerformance', + 'netPerformanceWithCurrencyEffect', + 'platforms[*].balance', + 'platforms[*].valueInBaseCurrency', + 'quantity', + 'SymbolProfile.symbolMapping', + 'SymbolProfile.watchedByCount', + 'totalBalanceInBaseCurrency', + 'totalDividendInBaseCurrency', + 'totalInterestInBaseCurrency', + 'totalValueInBaseCurrency', + 'value', + 'valueInBaseCurrency' +]; + // USX is handled separately export const DERIVED_CURRENCIES = [ { diff --git a/package-lock.json b/package-lock.json index 38789fd53..d5e3c39fa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -62,6 +62,7 @@ "dotenv": "17.2.3", "dotenv-expand": "12.0.3", "envalid": "8.1.1", + "fast-redact": "3.5.0", "fuse.js": "7.1.0", "google-spreadsheet": "3.2.0", "helmet": "7.0.0", @@ -122,6 +123,7 @@ "@storybook/angular": "10.1.10", "@trivago/prettier-plugin-sort-imports": "5.2.2", "@types/big.js": "6.2.2", + "@types/fast-redact": "3.0.4", "@types/google-spreadsheet": "3.1.5", "@types/jest": "30.0.0", "@types/jsonpath": "0.2.4", @@ -12936,6 +12938,13 @@ "@types/send": "*" } }, + "node_modules/@types/fast-redact": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/fast-redact/-/fast-redact-3.0.4.tgz", + "integrity": "sha512-tgGJaXucrCH4Yx2l/AI6e/JQksZhKGIQsVwBMTh+nxUhQDv5tXScTs5DHTw+qSKDXnHL2dTAh1e2rd5pcFQyNQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/geojson": { "version": "7946.0.16", "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", @@ -19940,6 +19949,15 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "license": "MIT" }, + "node_modules/fast-redact": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.5.0.tgz", + "integrity": "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/fast-safe-stringify": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", diff --git a/package.json b/package.json index 9dc3a9914..5452f3e95 100644 --- a/package.json +++ b/package.json @@ -106,6 +106,7 @@ "dotenv": "17.2.3", "dotenv-expand": "12.0.3", "envalid": "8.1.1", + "fast-redact": "3.5.0", "fuse.js": "7.1.0", "google-spreadsheet": "3.2.0", "helmet": "7.0.0", @@ -166,6 +167,7 @@ "@storybook/angular": "10.1.10", "@trivago/prettier-plugin-sort-imports": "5.2.2", "@types/big.js": "6.2.2", + "@types/fast-redact": "3.0.4", "@types/google-spreadsheet": "3.1.5", "@types/jest": "30.0.0", "@types/jsonpath": "0.2.4",