|
diff --git a/apps/client/src/app/components/admin-tag/admin-tag.component.ts b/apps/client/src/app/components/admin-tag/admin-tag.component.ts
index 12ac1f368b..b08b144cf8 100644
--- a/apps/client/src/app/components/admin-tag/admin-tag.component.ts
+++ b/apps/client/src/app/components/admin-tag/admin-tag.component.ts
@@ -7,6 +7,7 @@ import {
getLowercase,
isSystemTag
} from '@ghostfolio/common/helper';
+import { TagWithAccountAndActivityCount } from '@ghostfolio/common/types';
import { translate } from '@ghostfolio/ui/i18n';
import { NotificationService } from '@ghostfolio/ui/notifications';
import { DataService } from '@ghostfolio/ui/services';
@@ -32,7 +33,6 @@ import { MatSort, MatSortModule } from '@angular/material/sort';
import { MatTableDataSource, MatTableModule } from '@angular/material/table';
import { ActivatedRoute, Router, RouterModule } from '@angular/router';
import { IonIcon } from '@ionic/angular/standalone';
-import { Tag } from '@prisma/client';
import { addIcons } from 'ionicons';
import {
createOutline,
@@ -63,17 +63,20 @@ import { CreateOrUpdateTagDialogParams } from './create-or-update-tag-dialog/int
export class GfAdminTagComponent implements OnInit {
public readonly locale = input(getLocale());
- protected dataSource = new MatTableDataSource();
+ protected dataSource =
+ new MatTableDataSource();
+
protected readonly displayedColumns = [
'name',
'userId',
'accounts',
'activities',
+ 'total',
'actions'
];
protected readonly isSystemTag = isSystemTag;
protected readonly pageSize = DEFAULT_PAGE_SIZE;
- protected tags: Tag[];
+ protected tags: TagWithAccountAndActivityCount[];
protected readonly translate = translate;
private readonly deviceType = computed(
@@ -120,6 +123,13 @@ export class GfAdminTagComponent implements OnInit {
this.fetchTags();
}
+ protected getTotalCount({
+ accountCount,
+ activityCount
+ }: TagWithAccountAndActivityCount) {
+ return accountCount + activityCount;
+ }
+
protected onDeleteTag(aId: string) {
this.notificationService.confirm({
confirmFn: () => {
@@ -130,7 +140,7 @@ export class GfAdminTagComponent implements OnInit {
});
}
- protected onUpdateTag({ id }: Tag) {
+ protected onUpdateTag({ id }: TagWithAccountAndActivityCount) {
this.router.navigate([], {
queryParams: { editTagDialog: true, tagId: id }
});
@@ -164,9 +174,13 @@ export class GfAdminTagComponent implements OnInit {
this.dataSource.sort = this.sort();
this.dataSource.sortingDataAccessor = (tag, path) => {
- return path === 'name'
- ? translate(tag.name).toLocaleLowerCase()
- : (getLowercase(tag, path) as number | string);
+ if (path === 'name') {
+ return translate(tag.name).toLocaleLowerCase();
+ } else if (path === 'totalCount') {
+ return this.getTotalCount(tag);
+ }
+
+ return getLowercase(tag, path) as number | string;
};
this.dataService.updateInfo();
diff --git a/libs/common/src/lib/types/index.ts b/libs/common/src/lib/types/index.ts
index 9a44efc1af..fb0f6462c6 100644
--- a/libs/common/src/lib/types/index.ts
+++ b/libs/common/src/lib/types/index.ts
@@ -24,6 +24,7 @@ import type { PropertyKey } from './property-key.type';
import type { RequestWithUser } from './request-with-user.type';
import type { SectorName } from './sector-name.type';
import type { SubscriptionOfferKey } from './subscription-offer-key.type';
+import type { TagWithAccountAndActivityCount } from './tag-with-account-and-activity-count.type';
import type { ToggleOption } from './toggle-option.type';
import type { UserWithSettings } from './user-with-settings.type';
import type { ViewMode } from './view-mode.type';
@@ -55,6 +56,7 @@ export type {
RequestWithUser,
SectorName,
SubscriptionOfferKey,
+ TagWithAccountAndActivityCount,
ToggleOption,
UserWithSettings,
ViewMode
diff --git a/libs/common/src/lib/types/tag-with-account-and-activity-count.type.ts b/libs/common/src/lib/types/tag-with-account-and-activity-count.type.ts
new file mode 100644
index 0000000000..662e0f5ce9
--- /dev/null
+++ b/libs/common/src/lib/types/tag-with-account-and-activity-count.type.ts
@@ -0,0 +1,6 @@
+import { Tag } from '@prisma/client';
+
+export type TagWithAccountAndActivityCount = Tag & {
+ accountCount: number;
+ activityCount: number;
+};
diff --git a/libs/ui/src/lib/services/data.service.ts b/libs/ui/src/lib/services/data.service.ts
index 2b390df3c5..e09632079c 100644
--- a/libs/ui/src/lib/services/data.service.ts
+++ b/libs/ui/src/lib/services/data.service.ts
@@ -62,7 +62,8 @@ import { filterGlobalPermissions } from '@ghostfolio/common/permissions';
import type {
AiPromptMode,
DateRange,
- GroupBy
+ GroupBy,
+ TagWithAccountAndActivityCount
} from '@ghostfolio/common/types';
import { translate } from '@ghostfolio/ui/i18n';
@@ -843,7 +844,7 @@ export class DataService {
}
public fetchTags() {
- return this.http.get('/api/v1/tags');
+ return this.http.get('/api/v1/tags');
}
public fetchWatchlist() {
From c1c73e145a30357d787899ad165e9e7169b15423 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Szymon=20=C5=81=C4=85giewka?=
Date: Tue, 29 Sep 2026 19:40:51 +0200
Subject: [PATCH 07/27] Task/migrate client to prisma-client generated code
(#7868)
* Migrate client to prisma-client generated code
* Update changelog
---
.gitignore | 1 +
CHANGELOG.md | 4 ++++
apps/api/eslint.config.cjs | 5 ++++-
.../account-detail-dialog.component.ts | 2 +-
.../admin-market-data.component.ts | 3 ++-
.../asset-profile-dialog-host.component.ts | 2 +-
.../asset-profile-dialog.component.ts | 22 ++++++++++---------
.../interfaces/interfaces.ts | 3 +--
.../create-asset-profile-dialog.component.ts | 2 +-
.../admin-platform.component.ts | 2 +-
.../interfaces/interfaces.ts | 2 +-
.../interfaces/interfaces.ts | 2 +-
.../benchmark-comparator.component.ts | 2 +-
.../data-provider-status.component.ts | 2 +-
.../holding-detail-dialog.component.ts | 2 +-
.../interfaces/interfaces.ts | 3 +--
...reate-or-update-access-dialog.component.ts | 2 +-
.../user-detail-dialog.component.ts | 2 +-
apps/client/src/app/interfaces/interfaces.ts | 3 ++-
...eate-or-update-account-dialog.component.ts | 2 +-
.../interfaces/interfaces.ts | 3 +--
...ate-or-update-activity-dialog.component.ts | 3 ++-
.../types/activity-type.type.ts | 2 +-
.../import-activities-dialog.component.ts | 2 +-
.../interfaces/interfaces.ts | 3 +--
.../allocations/allocations-page.component.ts | 13 +++++------
.../analysis/analysis-page.component.ts | 2 +-
.../src/app/services/ics/ics.service.ts | 2 +-
.../app/services/import-activities.service.ts | 3 ++-
eslint.config.cjs | 12 ++++++++++
libs/common/src/lib/config.ts | 8 ++++++-
libs/common/src/lib/dtos/create-access.dto.ts | 2 +-
...eate-asset-profile-with-market-data.dto.ts | 2 +-
.../src/lib/dtos/create-asset-profile.dto.ts | 7 +++++-
libs/common/src/lib/dtos/create-order.dto.ts | 7 +++++-
.../src/lib/dtos/create-watchlist-item.dto.ts | 2 +-
.../src/lib/dtos/merge-asset-profile.dto.ts | 3 ++-
.../lib/dtos/update-asset-profile-data.dto.ts | 3 ++-
.../src/lib/dtos/update-asset-profile.dto.ts | 8 +++----
libs/common/src/lib/dtos/update-order.dto.ts | 7 +++++-
libs/common/src/lib/helper.ts | 17 ++++++++------
.../src/lib/interfaces/access.interface.ts | 2 +-
.../lib/interfaces/activities.interface.ts | 3 +--
.../admin-market-data-details.interface.ts | 2 +-
.../lib/interfaces/admin-user.interface.ts | 3 ++-
.../asset-class-selector-option.interface.ts | 2 +-
.../asset-profile-identifier.interface.ts | 2 +-
.../asset-profile-item.interface.ts | 6 ++++-
.../data-provider-info.interface.ts | 2 +-
.../enhanced-asset-profile.interface.ts | 2 +-
.../src/lib/interfaces/info-item.interface.ts | 2 +-
.../lib/interfaces/lookup-item.interface.ts | 6 ++++-
.../portfolio-position.interface.ts | 3 +--
.../account-balances-response.interface.ts | 2 +-
.../asset-profile-response.interface.ts | 2 +-
...tfolio-asset-profile-response.interface.ts | 2 +-
.../data-provider-response.interface.ts | 3 +--
.../responses/export-response.interface.ts | 8 ++++++-
.../responses/platforms-response.interface.ts | 2 +-
.../portfolio-holding-response.interface.ts | 3 +--
.../public-portfolio-response.interface.ts | 3 +--
.../src/lib/interfaces/user-item.interface.ts | 2 +-
.../lib/interfaces/user-settings.interface.ts | 3 +--
.../src/lib/interfaces/user.interface.ts | 4 ++--
.../lib/models/portfolio-snapshot-holding.ts | 3 ++-
libs/common/src/lib/permissions.ts | 3 +--
libs/common/src/lib/scopes.ts | 3 +--
.../types/access-with-grantee-user.type.ts | 2 +-
.../lib/types/account-with-balance.type.ts | 2 +-
.../lib/types/account-with-platform.type.ts | 2 +-
.../src/lib/types/account-with-value.type.ts | 2 +-
.../src/lib/types/order-with-account.type.ts | 2 +-
...ag-with-account-and-activity-count.type.ts | 2 +-
.../src/lib/types/user-with-settings.type.ts | 3 +--
.../activities-table.component.ts | 2 +-
.../activity-type/activity-type.component.ts | 3 ++-
.../interfaces/interfaces.ts | 3 +--
.../lib/entity-logo/entity-logo.component.ts | 2 +-
.../interfaces/interfaces.ts | 3 +--
...historical-market-data-editor.component.ts | 3 ++-
.../entity-logo-image-source.service.mock.ts | 3 +--
.../portfolio-filter-form.util.ts | 4 ++--
.../portfolio-proportion-chart.component.ts | 2 +-
libs/ui/src/lib/services/admin.service.ts | 6 ++++-
libs/ui/src/lib/services/data.service.ts | 18 +++++++--------
.../tags-selector/interfaces/interfaces.ts | 2 +-
nx.json | 3 ++-
prisma/schema.prisma | 9 ++++++--
tsconfig.base.json | 2 ++
89 files changed, 201 insertions(+), 136 deletions(-)
diff --git a/.gitignore b/.gitignore
index 58520c230b..5699a5929d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,7 @@
# compiled output
/out-tsc
+/prisma/generated
/tmp
# dependencies
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ff42504d49..d90f853cd0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added the total column to the table of the tag management in the admin control panel
+### Changed
+
+- Migrated the client to the code generated by the new `prisma-client` generator of _Prisma_
+
## 3.75.0 - 2026-09-28
### Changed
diff --git a/apps/api/eslint.config.cjs b/apps/api/eslint.config.cjs
index 043802a0df..07054e44b9 100644
--- a/apps/api/eslint.config.cjs
+++ b/apps/api/eslint.config.cjs
@@ -21,7 +21,10 @@ module.exports = [
{
files: ['**/*.ts', '**/*.tsx'],
// Override or add rules here
- rules: {}
+ rules: {
+ // TODO: Remove once the API uses the code generated by the new prisma-client generator
+ '@typescript-eslint/no-restricted-imports': 'off'
+ }
},
{
files: ['**/*.js', '**/*.jsx'],
diff --git a/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts b/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts
index 75186b1744..b8c7f16dbc 100644
--- a/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts
+++ b/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts
@@ -16,6 +16,7 @@ import {
} from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { hasScope, scopes } from '@ghostfolio/common/scopes';
+import type { Tag } from '@ghostfolio/prisma/browser';
import { GfAccountBalancesComponent } from '@ghostfolio/ui/account-balances';
import { GfActivitiesTableComponent } from '@ghostfolio/ui/activities-table';
import { GfDialogFooterComponent } from '@ghostfolio/ui/dialog-footer';
@@ -45,7 +46,6 @@ import { MatTableDataSource } from '@angular/material/table';
import { MatTabsModule } from '@angular/material/tabs';
import { NavigationStart, Router } from '@angular/router';
import { IonIcon } from '@ionic/angular/standalone';
-import { Tag } from '@prisma/client';
import { Big } from 'big.js';
import { format, parseISO } from 'date-fns';
import { addIcons } from 'ionicons';
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 c01932b603..d1ea9d97eb 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
@@ -10,6 +10,8 @@ import {
} from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { internalRoutes } from '@ghostfolio/common/routes/routes';
+import type { SymbolProfile } from '@ghostfolio/prisma/browser';
+import { AssetSubClass, DataSource } from '@ghostfolio/prisma/enums';
import { GfActivitiesFilterComponent } from '@ghostfolio/ui/activities-filter';
import { GfFabComponent } from '@ghostfolio/ui/fab';
import { translate } from '@ghostfolio/ui/i18n';
@@ -48,7 +50,6 @@ import {
import { MatTableDataSource, MatTableModule } from '@angular/material/table';
import { Router, RouterModule } from '@angular/router';
import { IonIcon } from '@ionic/angular/standalone';
-import { AssetSubClass, DataSource, SymbolProfile } from '@prisma/client';
import { isUUID } from 'class-validator';
import { addIcons } from 'ionicons';
import {
diff --git a/apps/client/src/app/components/admin-market-data/asset-profile-dialog-host/asset-profile-dialog-host.component.ts b/apps/client/src/app/components/admin-market-data/asset-profile-dialog-host/asset-profile-dialog-host.component.ts
index 7b03762ff9..af83ef4a7d 100644
--- a/apps/client/src/app/components/admin-market-data/asset-profile-dialog-host/asset-profile-dialog-host.component.ts
+++ b/apps/client/src/app/components/admin-market-data/asset-profile-dialog-host/asset-profile-dialog-host.component.ts
@@ -5,6 +5,7 @@ import {
} from '@ghostfolio/common/config';
import { AssetProfileIdentifier, User } from '@ghostfolio/common/interfaces';
import { internalRoutes } from '@ghostfolio/common/routes/routes';
+import { DataSource } from '@ghostfolio/prisma/enums';
import { AdminService } from '@ghostfolio/ui/services';
import {
@@ -19,7 +20,6 @@ import {
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { MatDialog, MatDialogRef } from '@angular/material/dialog';
import { ActivatedRoute, Router } from '@angular/router';
-import { DataSource } from '@prisma/client';
import { DeviceDetectorService } from 'ngx-device-detector';
import { Subject } from 'rxjs';
import { distinctUntilChanged, map, takeUntil, tap } from 'rxjs/operators';
diff --git a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts
index b2b2293e1a..154211b918 100644
--- a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts
+++ b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts
@@ -29,6 +29,18 @@ import {
} from '@ghostfolio/common/interfaces';
import { DateRange } from '@ghostfolio/common/types';
import { jsonValidator, validateObjectForForm } from '@ghostfolio/common/utils';
+import type {
+ AssetProfileSplit,
+ MarketData,
+ Prisma,
+ SymbolProfile
+} from '@ghostfolio/prisma/browser';
+import {
+ AssetClass,
+ AssetSubClass,
+ DataGatheringFrequency,
+ DataSource
+} from '@ghostfolio/prisma/enums';
import { GfCurrencySelectorComponent } from '@ghostfolio/ui/currency-selector';
import { GfEntityLogoComponent } from '@ghostfolio/ui/entity-logo';
import { GfHistoricalMarketDataEditorComponent } from '@ghostfolio/ui/historical-market-data-editor';
@@ -80,16 +92,6 @@ import { MatSelectModule } from '@angular/material/select';
import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar';
import { MatTabsModule } from '@angular/material/tabs';
import { IonIcon } from '@ionic/angular/standalone';
-import {
- AssetClass,
- AssetProfileSplit,
- AssetSubClass,
- DataGatheringFrequency,
- DataSource,
- MarketData,
- Prisma,
- SymbolProfile
-} from '@prisma/client';
import { isUUID } from 'class-validator';
import { format } from 'date-fns';
import { StatusCodes } from 'http-status-codes';
diff --git a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/interfaces/interfaces.ts b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/interfaces/interfaces.ts
index 6a966b4270..0f000364ee 100644
--- a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/interfaces/interfaces.ts
+++ b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/interfaces/interfaces.ts
@@ -1,6 +1,5 @@
import { ColorScheme } from '@ghostfolio/common/types';
-
-import { DataSource } from '@prisma/client';
+import { DataSource } from '@ghostfolio/prisma/enums';
export interface AssetProfileDialogParams {
colorScheme: ColorScheme;
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 b948708c2e..8b7ef793fc 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
@@ -4,6 +4,7 @@ import {
PROPERTY_CURRENCIES
} from '@ghostfolio/common/config';
import type { AssetProfileIdentifier } from '@ghostfolio/common/interfaces';
+import { DataSource } from '@ghostfolio/prisma/enums';
import { AdminService, DataService } from '@ghostfolio/ui/services';
import { GfSymbolAutocompleteComponent } from '@ghostfolio/ui/symbol-autocomplete';
@@ -31,7 +32,6 @@ import { MatDialogModule, MatDialogRef } from '@angular/material/dialog';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { MatRadioModule } from '@angular/material/radio';
-import { DataSource } from '@prisma/client';
import { isISO4217CurrencyCode } from 'class-validator';
import { switchMap } from 'rxjs';
diff --git a/apps/client/src/app/components/admin-platform/admin-platform.component.ts b/apps/client/src/app/components/admin-platform/admin-platform.component.ts
index 00b5ff0db2..0e6ed66d9b 100644
--- a/apps/client/src/app/components/admin-platform/admin-platform.component.ts
+++ b/apps/client/src/app/components/admin-platform/admin-platform.component.ts
@@ -3,6 +3,7 @@ import { DEFAULT_PAGE_SIZE } from '@ghostfolio/common/config';
import { CreatePlatformDto, UpdatePlatformDto } from '@ghostfolio/common/dtos';
import { ConfirmationDialogType } from '@ghostfolio/common/enums';
import { getLocale, getLowercase } from '@ghostfolio/common/helper';
+import type { Platform } from '@ghostfolio/prisma/browser';
import { GfEntityLogoComponent } from '@ghostfolio/ui/entity-logo';
import { NotificationService } from '@ghostfolio/ui/notifications';
import { AdminService, DataService } from '@ghostfolio/ui/services';
@@ -28,7 +29,6 @@ import { MatSort, MatSortModule } from '@angular/material/sort';
import { MatTableDataSource, MatTableModule } from '@angular/material/table';
import { ActivatedRoute, Router, RouterModule } from '@angular/router';
import { IonIcon } from '@ionic/angular/standalone';
-import { Platform } from '@prisma/client';
import { addIcons } from 'ionicons';
import {
createOutline,
diff --git a/apps/client/src/app/components/admin-platform/create-or-update-platform-dialog/interfaces/interfaces.ts b/apps/client/src/app/components/admin-platform/create-or-update-platform-dialog/interfaces/interfaces.ts
index d3b9ae5288..206cc14e79 100644
--- a/apps/client/src/app/components/admin-platform/create-or-update-platform-dialog/interfaces/interfaces.ts
+++ b/apps/client/src/app/components/admin-platform/create-or-update-platform-dialog/interfaces/interfaces.ts
@@ -1,4 +1,4 @@
-import { Platform } from '@prisma/client';
+import type { Platform } from '@ghostfolio/prisma/browser';
export interface CreateOrUpdatePlatformDialogParams {
platform?: Platform;
diff --git a/apps/client/src/app/components/admin-tag/create-or-update-tag-dialog/interfaces/interfaces.ts b/apps/client/src/app/components/admin-tag/create-or-update-tag-dialog/interfaces/interfaces.ts
index f4b1104d00..b866ea47dd 100644
--- a/apps/client/src/app/components/admin-tag/create-or-update-tag-dialog/interfaces/interfaces.ts
+++ b/apps/client/src/app/components/admin-tag/create-or-update-tag-dialog/interfaces/interfaces.ts
@@ -1,4 +1,4 @@
-import { Tag } from '@prisma/client';
+import type { Tag } from '@ghostfolio/prisma/browser';
export interface CreateOrUpdateTagDialogParams {
tag?: Pick;
diff --git a/apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts b/apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts
index 8d5e3ff62b..ef14b404e4 100644
--- a/apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts
+++ b/apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts
@@ -12,6 +12,7 @@ import { LineChartItem, User } from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { internalRoutes } from '@ghostfolio/common/routes/routes';
import { ColorScheme } from '@ghostfolio/common/types';
+import type { SymbolProfile } from '@ghostfolio/prisma/browser';
import {
getTimeSeriesTooltipOptions,
registerChartConfiguration
@@ -32,7 +33,6 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatSelectModule } from '@angular/material/select';
import { RouterModule } from '@angular/router';
import { IonIcon } from '@ionic/angular/standalone';
-import { SymbolProfile } from '@prisma/client';
import {
Chart,
ChartData,
diff --git a/apps/client/src/app/components/data-provider-status/data-provider-status.component.ts b/apps/client/src/app/components/data-provider-status/data-provider-status.component.ts
index a77d65961a..13218623a0 100644
--- a/apps/client/src/app/components/data-provider-status/data-provider-status.component.ts
+++ b/apps/client/src/app/components/data-provider-status/data-provider-status.component.ts
@@ -1,3 +1,4 @@
+import type { DataSource } from '@ghostfolio/prisma/enums';
import { DataService } from '@ghostfolio/ui/services';
import { CommonModule } from '@angular/common';
@@ -9,7 +10,6 @@ import {
OnInit
} from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
-import type { DataSource } from '@prisma/client';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
import { catchError, map, type Observable, of } from 'rxjs';
diff --git a/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts
index d8385d70e2..4a16c1a4e2 100644
--- a/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts
+++ b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts
@@ -25,6 +25,7 @@ import {
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { internalRoutes } from '@ghostfolio/common/routes/routes';
import { AccountWithValue } from '@ghostfolio/common/types';
+import type { MarketData, Tag } from '@ghostfolio/prisma/browser';
import { GfAccountsTableComponent } from '@ghostfolio/ui/accounts-table';
import { GfActivitiesTableComponent } from '@ghostfolio/ui/activities-table';
import { GfDataProviderCreditsComponent } from '@ghostfolio/ui/data-provider-credits';
@@ -69,7 +70,6 @@ import { MatTableDataSource } from '@angular/material/table';
import { MatTabsModule } from '@angular/material/tabs';
import { NavigationStart, Router, RouterModule } from '@angular/router';
import { IonIcon } from '@ionic/angular/standalone';
-import { MarketData, Tag } from '@prisma/client';
import { isUUID } from 'class-validator';
import { format, isSameMonth, isToday, parseISO } from 'date-fns';
import { addIcons } from 'ionicons';
diff --git a/apps/client/src/app/components/holding-detail-dialog/interfaces/interfaces.ts b/apps/client/src/app/components/holding-detail-dialog/interfaces/interfaces.ts
index efc10d6ac3..5f33db42a3 100644
--- a/apps/client/src/app/components/holding-detail-dialog/interfaces/interfaces.ts
+++ b/apps/client/src/app/components/holding-detail-dialog/interfaces/interfaces.ts
@@ -1,6 +1,5 @@
import { ColorScheme } from '@ghostfolio/common/types';
-
-import { DataSource } from '@prisma/client';
+import { DataSource } from '@ghostfolio/prisma/enums';
export interface HoldingDetailDialogParams {
baseCurrency: string;
diff --git a/apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.component.ts b/apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.component.ts
index 9f119fc439..f58b19212b 100644
--- a/apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.component.ts
+++ b/apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.component.ts
@@ -15,6 +15,7 @@ import {
} from '@ghostfolio/common/scopes';
import { AccessLevel, AccountWithPlatform } from '@ghostfolio/common/types';
import { validateObjectForForm } from '@ghostfolio/common/utils';
+import { AccessType } from '@ghostfolio/prisma/enums';
import { GfAccessLevelIconComponent } from '@ghostfolio/ui/access-level-icon';
import { NotificationService } from '@ghostfolio/ui/notifications';
import {
@@ -57,7 +58,6 @@ import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { MatSelectModule } from '@angular/material/select';
import { IonIcon } from '@ionic/angular/standalone';
-import { AccessType } from '@prisma/client';
import { addDays, endOfDay, isValid, startOfDay } from 'date-fns';
import { StatusCodes } from 'http-status-codes';
import { addIcons } from 'ionicons';
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 3aec4d42c7..926fb8d8cd 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
@@ -4,6 +4,7 @@ import {
getSum
} from '@ghostfolio/common/helper';
import { AdminUserResponse } from '@ghostfolio/common/interfaces';
+import type { Subscription } from '@ghostfolio/prisma/browser';
import { AdminService, DataService } from '@ghostfolio/ui/services';
import { GfValueComponent } from '@ghostfolio/ui/value';
@@ -23,7 +24,6 @@ import { MatDialogModule } from '@angular/material/dialog';
import { MatMenuModule } from '@angular/material/menu';
import { MatTableDataSource, MatTableModule } from '@angular/material/table';
import { IonIcon } from '@ionic/angular/standalone';
-import { Subscription } from '@prisma/client';
import { Big } from 'big.js';
import { differenceInDays } from 'date-fns';
import { addIcons } from 'ionicons';
diff --git a/apps/client/src/app/interfaces/interfaces.ts b/apps/client/src/app/interfaces/interfaces.ts
index 493eff4ab4..191fb42290 100644
--- a/apps/client/src/app/interfaces/interfaces.ts
+++ b/apps/client/src/app/interfaces/interfaces.ts
@@ -1,5 +1,6 @@
+import type { DataSource } from '@ghostfolio/prisma/enums';
+
import type { Params } from '@angular/router';
-import type { DataSource } from '@prisma/client';
export interface GfAppQueryParams extends Params {
dataSource?: DataSource;
diff --git a/apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.component.ts b/apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.component.ts
index ab3854b931..12f9075247 100644
--- a/apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.component.ts
+++ b/apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.component.ts
@@ -8,6 +8,7 @@ import { CreateAccountDto, UpdateAccountDto } from '@ghostfolio/common/dtos';
import { getStringOrNull } from '@ghostfolio/common/helper';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { validateObjectForForm } from '@ghostfolio/common/utils';
+import type { Platform, Tag } from '@ghostfolio/prisma/browser';
import { GfCurrencySelectorComponent } from '@ghostfolio/ui/currency-selector';
import { GfEntityLogoComponent } from '@ghostfolio/ui/entity-logo';
import { translate } from '@ghostfolio/ui/i18n';
@@ -39,7 +40,6 @@ import {
} from '@angular/material/dialog';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
-import { Platform, Tag } from '@prisma/client';
import { Observable } from 'rxjs';
import { map, startWith } from 'rxjs/operators';
diff --git a/apps/client/src/app/pages/accounts/create-or-update-account-dialog/interfaces/interfaces.ts b/apps/client/src/app/pages/accounts/create-or-update-account-dialog/interfaces/interfaces.ts
index 43015de004..c99cd17b7a 100644
--- a/apps/client/src/app/pages/accounts/create-or-update-account-dialog/interfaces/interfaces.ts
+++ b/apps/client/src/app/pages/accounts/create-or-update-account-dialog/interfaces/interfaces.ts
@@ -1,7 +1,6 @@
import { User } from '@ghostfolio/common/interfaces';
import { AccountWithBalance } from '@ghostfolio/common/types';
-
-import { Tag } from '@prisma/client';
+import type { Tag } from '@ghostfolio/prisma/browser';
export interface CreateOrUpdateAccountDialogParams {
account: Omit<
diff --git a/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts b/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts
index 3b7938c8c4..f24297dbd0 100644
--- a/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts
+++ b/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts
@@ -16,6 +16,8 @@ import {
} from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { validateObjectForForm } from '@ghostfolio/common/utils';
+import type { Tag } from '@ghostfolio/prisma/browser';
+import { AssetClass, Type } from '@ghostfolio/prisma/enums';
import { GfAccountSelectorComponent } from '@ghostfolio/ui/account-selector';
import { translate } from '@ghostfolio/ui/i18n';
import { DataService } from '@ghostfolio/ui/services';
@@ -50,7 +52,6 @@ import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { MatSelectModule } from '@angular/material/select';
import { IonIcon } from '@ionic/angular/standalone';
-import { AssetClass, Tag, Type } from '@prisma/client';
import { isAfter, isToday } from 'date-fns';
import { addIcons } from 'ionicons';
import { calendarClearOutline, refreshOutline } from 'ionicons/icons';
diff --git a/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/types/activity-type.type.ts b/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/types/activity-type.type.ts
index 6c3f32f284..cfb8452da4 100644
--- a/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/types/activity-type.type.ts
+++ b/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/types/activity-type.type.ts
@@ -1,4 +1,4 @@
-import { Type } from '@prisma/client';
+import { Type } from '@ghostfolio/prisma/enums';
export const ActivityType = {
...Type,
diff --git a/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts b/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts
index 98e0c0ad82..26255f2061 100644
--- a/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts
+++ b/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts
@@ -8,6 +8,7 @@ import {
CreateTagDto
} from '@ghostfolio/common/dtos';
import { Activity, PortfolioPosition } from '@ghostfolio/common/interfaces';
+import { AssetClass } from '@ghostfolio/prisma/enums';
import { GfActivitiesTableComponent } from '@ghostfolio/ui/activities-table';
import { GfDialogFooterComponent } from '@ghostfolio/ui/dialog-footer';
import { GfDialogHeaderComponent } from '@ghostfolio/ui/dialog-header';
@@ -48,7 +49,6 @@ import { SortDirection } from '@angular/material/sort';
import { MatStepper, MatStepperModule } from '@angular/material/stepper';
import { MatTableDataSource } from '@angular/material/table';
import { IonIcon } from '@ionic/angular/standalone';
-import { AssetClass } from '@prisma/client';
import { addIcons } from 'ionicons';
import { cloudUploadOutline, warningOutline } from 'ionicons/icons';
import { isArray, sortBy } from 'lodash';
diff --git a/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/interfaces/interfaces.ts b/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/interfaces/interfaces.ts
index 051345e603..39749b1222 100644
--- a/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/interfaces/interfaces.ts
+++ b/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/interfaces/interfaces.ts
@@ -1,6 +1,5 @@
import { User } from '@ghostfolio/common/interfaces';
-
-import { Type } from '@prisma/client';
+import { Type } from '@ghostfolio/prisma/enums';
export interface ImportActivitiesDialogParams {
activityTypes?: Type[];
diff --git a/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts b/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts
index ceda1d4c07..6d67b0253c 100644
--- a/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts
+++ b/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts
@@ -22,6 +22,12 @@ import {
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { hasScope, scopes } from '@ghostfolio/common/scopes';
import { MarketAdvanced } from '@ghostfolio/common/types';
+import type { Account, Platform } from '@ghostfolio/prisma/browser';
+import {
+ AssetClass,
+ AssetSubClass,
+ DataSource
+} from '@ghostfolio/prisma/enums';
import { translate } from '@ghostfolio/ui/i18n';
import {
GfPortfolioProportionChartComponent,
@@ -47,13 +53,6 @@ import { MatCardModule } from '@angular/material/card';
import { MatDialog } from '@angular/material/dialog';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { ActivatedRoute, Router } from '@angular/router';
-import {
- Account,
- AssetClass,
- AssetSubClass,
- DataSource,
- Platform
-} from '@prisma/client';
import { isNumber } from 'lodash';
import { DeviceDetectorService } from 'ngx-device-detector';
import { filter, switchMap, tap } from 'rxjs';
diff --git a/apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts b/apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts
index 450749925e..898c7f51a8 100644
--- a/apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts
+++ b/apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts
@@ -22,6 +22,7 @@ import type {
ToggleOption
} from '@ghostfolio/common/types';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
+import type { SymbolProfile } from '@ghostfolio/prisma/browser';
import { translate } from '@ghostfolio/ui/i18n';
import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator';
import { DataService } from '@ghostfolio/ui/services';
@@ -48,7 +49,6 @@ import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatSnackBar } from '@angular/material/snack-bar';
import { RouterModule } from '@angular/router';
import { IonIcon } from '@ionic/angular/standalone';
-import { SymbolProfile } from '@prisma/client';
import { addIcons } from 'ionicons';
import { copyOutline, ellipsisVertical } from 'ionicons/icons';
import { isNumber, keyBy, sortBy, union } from 'lodash';
diff --git a/apps/client/src/app/services/ics/ics.service.ts b/apps/client/src/app/services/ics/ics.service.ts
index d97b427c9e..90170dbb75 100644
--- a/apps/client/src/app/services/ics/ics.service.ts
+++ b/apps/client/src/app/services/ics/ics.service.ts
@@ -1,8 +1,8 @@
import { capitalize } from '@ghostfolio/common/helper';
import { ExportResponse } from '@ghostfolio/common/interfaces';
+import { Type } from '@ghostfolio/prisma/enums';
import { Service } from '@angular/core';
-import { Type } from '@prisma/client';
import { format, parseISO } from 'date-fns';
@Service()
diff --git a/apps/client/src/app/services/import-activities.service.ts b/apps/client/src/app/services/import-activities.service.ts
index 5c06b0e136..599c66551a 100644
--- a/apps/client/src/app/services/import-activities.service.ts
+++ b/apps/client/src/app/services/import-activities.service.ts
@@ -11,10 +11,11 @@ import {
parseDate as parseDateHelper
} from '@ghostfolio/common/helper';
import { Activity } from '@ghostfolio/common/interfaces';
+import type { Account } from '@ghostfolio/prisma/browser';
+import { DataSource, Type as ActivityType } from '@ghostfolio/prisma/enums';
import { HttpClient } from '@angular/common/http';
import { inject, Service } from '@angular/core';
-import { Account, DataSource, Type as ActivityType } from '@prisma/client';
import { isFinite, isNumber, isString } from 'lodash';
import { parse as csvToJson } from 'papaparse';
import { firstValueFrom } from 'rxjs';
diff --git a/eslint.config.cjs b/eslint.config.cjs
index 1b4aa6a0c0..ee5e0a557c 100644
--- a/eslint.config.cjs
+++ b/eslint.config.cjs
@@ -83,6 +83,18 @@ module.exports = [
}
],
'@typescript-eslint/no-non-null-assertion': 'warn',
+ '@typescript-eslint/no-restricted-imports': [
+ 'error',
+ {
+ paths: [
+ {
+ message:
+ 'Import the types from @ghostfolio/prisma/browser and the enums from @ghostfolio/prisma/enums instead.',
+ name: '@prisma/client'
+ }
+ ]
+ }
+ ],
'@typescript-eslint/no-shadow': [
'warn',
{
diff --git a/libs/common/src/lib/config.ts b/libs/common/src/lib/config.ts
index 77e8542398..beedb741cb 100644
--- a/libs/common/src/lib/config.ts
+++ b/libs/common/src/lib/config.ts
@@ -1,4 +1,10 @@
-import { AssetClass, AssetSubClass, DataSource, Type } from '@prisma/client';
+import {
+ AssetClass,
+ AssetSubClass,
+ DataSource,
+ Type
+} from '@ghostfolio/prisma/enums';
+
import { JobOptions, JobStatus } from 'bull';
import ms from 'ms';
diff --git a/libs/common/src/lib/dtos/create-access.dto.ts b/libs/common/src/lib/dtos/create-access.dto.ts
index 6fee6fcb66..82feac222c 100644
--- a/libs/common/src/lib/dtos/create-access.dto.ts
+++ b/libs/common/src/lib/dtos/create-access.dto.ts
@@ -1,8 +1,8 @@
import { Filter } from '@ghostfolio/common/interfaces';
import { Scope, scopes } from '@ghostfolio/common/scopes';
import { IsInTheFutureConstraint } from '@ghostfolio/common/validator-constraints/is-in-the-future';
+import { AccessType } from '@ghostfolio/prisma/enums';
-import { AccessType } from '@prisma/client';
import {
IsArray,
IsEnum,
diff --git a/libs/common/src/lib/dtos/create-asset-profile-with-market-data.dto.ts b/libs/common/src/lib/dtos/create-asset-profile-with-market-data.dto.ts
index 60afcb558b..096f9e27ca 100644
--- a/libs/common/src/lib/dtos/create-asset-profile-with-market-data.dto.ts
+++ b/libs/common/src/lib/dtos/create-asset-profile-with-market-data.dto.ts
@@ -1,6 +1,6 @@
import { MarketData } from '@ghostfolio/common/interfaces';
+import { DataSource } from '@ghostfolio/prisma/enums';
-import { DataSource } from '@prisma/client';
import { IsArray, IsIn, IsOptional } from 'class-validator';
import { CreateAssetProfileDto } from './create-asset-profile.dto';
diff --git a/libs/common/src/lib/dtos/create-asset-profile.dto.ts b/libs/common/src/lib/dtos/create-asset-profile.dto.ts
index 4197137fb3..b4fe347071 100644
--- a/libs/common/src/lib/dtos/create-asset-profile.dto.ts
+++ b/libs/common/src/lib/dtos/create-asset-profile.dto.ts
@@ -3,8 +3,13 @@ import {
SYMBOL_MAXIMUM_LENGTH
} from '@ghostfolio/common/config';
import { IsCurrencyCode } from '@ghostfolio/common/validators/is-currency-code';
+import type { Prisma } from '@ghostfolio/prisma/browser';
+import {
+ AssetClass,
+ AssetSubClass,
+ DataSource
+} from '@ghostfolio/prisma/enums';
-import { AssetClass, AssetSubClass, DataSource, Prisma } from '@prisma/client';
import { Type } from 'class-transformer';
import {
IsArray,
diff --git a/libs/common/src/lib/dtos/create-order.dto.ts b/libs/common/src/lib/dtos/create-order.dto.ts
index 1345f418a1..fd062b610b 100644
--- a/libs/common/src/lib/dtos/create-order.dto.ts
+++ b/libs/common/src/lib/dtos/create-order.dto.ts
@@ -4,8 +4,13 @@ import {
} from '@ghostfolio/common/config';
import { IsAfter1970Constraint } from '@ghostfolio/common/validator-constraints/is-after-1970';
import { IsCurrencyCode } from '@ghostfolio/common/validators/is-currency-code';
+import {
+ AssetClass,
+ AssetSubClass,
+ DataSource,
+ Type
+} from '@ghostfolio/prisma/enums';
-import { AssetClass, AssetSubClass, DataSource, Type } from '@prisma/client';
import { Transform, TransformFnParams } from 'class-transformer';
import {
ArrayUnique,
diff --git a/libs/common/src/lib/dtos/create-watchlist-item.dto.ts b/libs/common/src/lib/dtos/create-watchlist-item.dto.ts
index 78808a0246..ccf9414fa1 100644
--- a/libs/common/src/lib/dtos/create-watchlist-item.dto.ts
+++ b/libs/common/src/lib/dtos/create-watchlist-item.dto.ts
@@ -1,6 +1,6 @@
import { SYMBOL_MAXIMUM_LENGTH } from '@ghostfolio/common/config';
+import { DataSource } from '@ghostfolio/prisma/enums';
-import { DataSource } from '@prisma/client';
import { IsEnum, IsString, MaxLength } from 'class-validator';
export class CreateWatchlistItemDto {
diff --git a/libs/common/src/lib/dtos/merge-asset-profile.dto.ts b/libs/common/src/lib/dtos/merge-asset-profile.dto.ts
index f7b74bc4e2..9df79a6b8c 100644
--- a/libs/common/src/lib/dtos/merge-asset-profile.dto.ts
+++ b/libs/common/src/lib/dtos/merge-asset-profile.dto.ts
@@ -1,4 +1,5 @@
-import { DataSource } from '@prisma/client';
+import { DataSource } from '@ghostfolio/prisma/enums';
+
import { IsEnum, IsString } from 'class-validator';
export class MergeAssetProfileDto {
diff --git a/libs/common/src/lib/dtos/update-asset-profile-data.dto.ts b/libs/common/src/lib/dtos/update-asset-profile-data.dto.ts
index a2f600fcd0..f94ea40fce 100644
--- a/libs/common/src/lib/dtos/update-asset-profile-data.dto.ts
+++ b/libs/common/src/lib/dtos/update-asset-profile-data.dto.ts
@@ -1,4 +1,5 @@
-import { Prisma } from '@prisma/client';
+import type { Prisma } from '@ghostfolio/prisma/browser';
+
import { IsArray, IsOptional } from 'class-validator';
export class UpdateAssetProfileDataDto {
diff --git a/libs/common/src/lib/dtos/update-asset-profile.dto.ts b/libs/common/src/lib/dtos/update-asset-profile.dto.ts
index 965778761d..6e48ef0e04 100644
--- a/libs/common/src/lib/dtos/update-asset-profile.dto.ts
+++ b/libs/common/src/lib/dtos/update-asset-profile.dto.ts
@@ -3,14 +3,14 @@ import {
SYMBOL_MAXIMUM_LENGTH
} from '@ghostfolio/common/config';
import { IsCurrencyCode } from '@ghostfolio/common/validators/is-currency-code';
-
+import type { Prisma } from '@ghostfolio/prisma/browser';
import {
AssetClass,
AssetSubClass,
DataGatheringFrequency,
- DataSource,
- Prisma
-} from '@prisma/client';
+ DataSource
+} from '@ghostfolio/prisma/enums';
+
import { Type } from 'class-transformer';
import {
IsArray,
diff --git a/libs/common/src/lib/dtos/update-order.dto.ts b/libs/common/src/lib/dtos/update-order.dto.ts
index 69d993559e..0e381f2069 100644
--- a/libs/common/src/lib/dtos/update-order.dto.ts
+++ b/libs/common/src/lib/dtos/update-order.dto.ts
@@ -4,8 +4,13 @@ import {
} from '@ghostfolio/common/config';
import { IsAfter1970Constraint } from '@ghostfolio/common/validator-constraints/is-after-1970';
import { IsCurrencyCode } from '@ghostfolio/common/validators/is-currency-code';
+import {
+ AssetClass,
+ AssetSubClass,
+ DataSource,
+ Type
+} from '@ghostfolio/prisma/enums';
-import { AssetClass, AssetSubClass, DataSource, Type } from '@prisma/client';
import { Transform, TransformFnParams } from 'class-transformer';
import {
ArrayUnique,
diff --git a/libs/common/src/lib/helper.ts b/libs/common/src/lib/helper.ts
index 6e50911322..69764935d1 100644
--- a/libs/common/src/lib/helper.ts
+++ b/libs/common/src/lib/helper.ts
@@ -1,14 +1,17 @@
-import { utc } from '@date-fns/utc';
-import { NumberParser } from '@internationalized/number';
-import {
- AccessType,
- Type as ActivityType,
+import type {
AssetProfileOverrides,
- AssetSubClass,
MarketData,
Prisma,
SymbolProfile
-} from '@prisma/client';
+} from '@ghostfolio/prisma/browser';
+import {
+ AccessType,
+ Type as ActivityType,
+ AssetSubClass
+} from '@ghostfolio/prisma/enums';
+
+import { utc } from '@date-fns/utc';
+import { NumberParser } from '@internationalized/number';
import { Big } from 'big.js';
import { isISO4217CurrencyCode, isUUID } from 'class-validator';
import { countries } from 'countries-list';
diff --git a/libs/common/src/lib/interfaces/access.interface.ts b/libs/common/src/lib/interfaces/access.interface.ts
index 040bc2a8cc..182a0f3a49 100644
--- a/libs/common/src/lib/interfaces/access.interface.ts
+++ b/libs/common/src/lib/interfaces/access.interface.ts
@@ -1,4 +1,4 @@
-import { AccessType } from '@prisma/client';
+import { AccessType } from '@ghostfolio/prisma/enums';
import { AccessSettings } from './access-settings.interface';
diff --git a/libs/common/src/lib/interfaces/activities.interface.ts b/libs/common/src/lib/interfaces/activities.interface.ts
index 4b8dd34350..2e9be921e3 100644
--- a/libs/common/src/lib/interfaces/activities.interface.ts
+++ b/libs/common/src/lib/interfaces/activities.interface.ts
@@ -1,7 +1,6 @@
import { EnhancedAssetProfile } from '@ghostfolio/common/interfaces';
import { AccountWithPlatform } from '@ghostfolio/common/types';
-
-import { Order, Tag } from '@prisma/client';
+import type { Order, Tag } from '@ghostfolio/prisma/browser';
export interface Activity extends Order {
account?: AccountWithPlatform;
diff --git a/libs/common/src/lib/interfaces/admin-market-data-details.interface.ts b/libs/common/src/lib/interfaces/admin-market-data-details.interface.ts
index 8cfe70f9c5..7a7bba7159 100644
--- a/libs/common/src/lib/interfaces/admin-market-data-details.interface.ts
+++ b/libs/common/src/lib/interfaces/admin-market-data-details.interface.ts
@@ -1,4 +1,4 @@
-import { AssetProfileSplit, MarketData } from '@prisma/client';
+import type { AssetProfileSplit, MarketData } from '@ghostfolio/prisma/browser';
import { EnhancedAssetProfile } from './enhanced-asset-profile.interface';
diff --git a/libs/common/src/lib/interfaces/admin-user.interface.ts b/libs/common/src/lib/interfaces/admin-user.interface.ts
index a7cda68a81..a5c46d92f8 100644
--- a/libs/common/src/lib/interfaces/admin-user.interface.ts
+++ b/libs/common/src/lib/interfaces/admin-user.interface.ts
@@ -1,4 +1,5 @@
-import { Provider, Role, Subscription } from '@prisma/client';
+import type { Subscription } from '@ghostfolio/prisma/browser';
+import { Provider, Role } from '@ghostfolio/prisma/enums';
export interface AdminUser {
accountCount: number;
diff --git a/libs/common/src/lib/interfaces/asset-class-selector-option.interface.ts b/libs/common/src/lib/interfaces/asset-class-selector-option.interface.ts
index b4b1060e43..ed8d580f9d 100644
--- a/libs/common/src/lib/interfaces/asset-class-selector-option.interface.ts
+++ b/libs/common/src/lib/interfaces/asset-class-selector-option.interface.ts
@@ -1,4 +1,4 @@
-import { AssetClass, AssetSubClass } from '@prisma/client';
+import { AssetClass, AssetSubClass } from '@ghostfolio/prisma/enums';
export interface AssetClassSelectorOption {
id: AssetClass | AssetSubClass;
diff --git a/libs/common/src/lib/interfaces/asset-profile-identifier.interface.ts b/libs/common/src/lib/interfaces/asset-profile-identifier.interface.ts
index 48fc18c2f4..a031f3a488 100644
--- a/libs/common/src/lib/interfaces/asset-profile-identifier.interface.ts
+++ b/libs/common/src/lib/interfaces/asset-profile-identifier.interface.ts
@@ -1,4 +1,4 @@
-import { DataSource } from '@prisma/client';
+import { DataSource } from '@ghostfolio/prisma/enums';
export interface AssetProfileIdentifier {
dataSource: DataSource;
diff --git a/libs/common/src/lib/interfaces/asset-profile-item.interface.ts b/libs/common/src/lib/interfaces/asset-profile-item.interface.ts
index 382e824cc6..a982f5050a 100644
--- a/libs/common/src/lib/interfaces/asset-profile-item.interface.ts
+++ b/libs/common/src/lib/interfaces/asset-profile-item.interface.ts
@@ -1,4 +1,8 @@
-import { AssetClass, AssetSubClass, DataSource } from '@prisma/client';
+import {
+ AssetClass,
+ AssetSubClass,
+ DataSource
+} from '@ghostfolio/prisma/enums';
import { DataProviderInfo } from './data-provider-info.interface';
diff --git a/libs/common/src/lib/interfaces/data-provider-info.interface.ts b/libs/common/src/lib/interfaces/data-provider-info.interface.ts
index 9fba0e62de..dd3c67d4f9 100644
--- a/libs/common/src/lib/interfaces/data-provider-info.interface.ts
+++ b/libs/common/src/lib/interfaces/data-provider-info.interface.ts
@@ -1,4 +1,4 @@
-import { DataSource } from '@prisma/client';
+import { DataSource } from '@ghostfolio/prisma/enums';
export interface DataProviderInfo {
dataSource?: DataSource;
diff --git a/libs/common/src/lib/interfaces/enhanced-asset-profile.interface.ts b/libs/common/src/lib/interfaces/enhanced-asset-profile.interface.ts
index 9e3a022f78..88226d9872 100644
--- a/libs/common/src/lib/interfaces/enhanced-asset-profile.interface.ts
+++ b/libs/common/src/lib/interfaces/enhanced-asset-profile.interface.ts
@@ -3,7 +3,7 @@ import {
AssetSubClass,
DataGatheringFrequency,
DataSource
-} from '@prisma/client';
+} from '@ghostfolio/prisma/enums';
import { Country } from './country.interface';
import { DataProviderInfo } from './data-provider-info.interface';
diff --git a/libs/common/src/lib/interfaces/info-item.interface.ts b/libs/common/src/lib/interfaces/info-item.interface.ts
index 96db7f9b06..355b19203c 100644
--- a/libs/common/src/lib/interfaces/info-item.interface.ts
+++ b/libs/common/src/lib/interfaces/info-item.interface.ts
@@ -1,4 +1,4 @@
-import { SymbolProfile } from '@prisma/client';
+import type { SymbolProfile } from '@ghostfolio/prisma/browser';
import { Statistics } from './statistics.interface';
import { SubscriptionOffer } from './subscription-offer.interface';
diff --git a/libs/common/src/lib/interfaces/lookup-item.interface.ts b/libs/common/src/lib/interfaces/lookup-item.interface.ts
index 6cedeca09e..5c653acddd 100644
--- a/libs/common/src/lib/interfaces/lookup-item.interface.ts
+++ b/libs/common/src/lib/interfaces/lookup-item.interface.ts
@@ -1,4 +1,8 @@
-import { AssetClass, AssetSubClass, DataSource } from '@prisma/client';
+import {
+ AssetClass,
+ AssetSubClass,
+ DataSource
+} from '@ghostfolio/prisma/enums';
import { DataProviderInfo } from './data-provider-info.interface';
diff --git a/libs/common/src/lib/interfaces/portfolio-position.interface.ts b/libs/common/src/lib/interfaces/portfolio-position.interface.ts
index 65d7ea1405..dcb150d963 100644
--- a/libs/common/src/lib/interfaces/portfolio-position.interface.ts
+++ b/libs/common/src/lib/interfaces/portfolio-position.interface.ts
@@ -1,6 +1,5 @@
import { Market, MarketAdvanced } from '@ghostfolio/common/types';
-
-import { Tag } from '@prisma/client';
+import type { Tag } from '@ghostfolio/prisma/browser';
import { EnhancedAssetProfile } from './enhanced-asset-profile.interface';
diff --git a/libs/common/src/lib/interfaces/responses/account-balances-response.interface.ts b/libs/common/src/lib/interfaces/responses/account-balances-response.interface.ts
index a623baaff9..f56cd6ce89 100644
--- a/libs/common/src/lib/interfaces/responses/account-balances-response.interface.ts
+++ b/libs/common/src/lib/interfaces/responses/account-balances-response.interface.ts
@@ -1,4 +1,4 @@
-import { AccountBalance } from '@prisma/client';
+import type { AccountBalance } from '@ghostfolio/prisma/browser';
export interface AccountBalancesResponse {
balances: (Pick & {
diff --git a/libs/common/src/lib/interfaces/responses/asset-profile-response.interface.ts b/libs/common/src/lib/interfaces/responses/asset-profile-response.interface.ts
index 26082c2c42..26ca5b29f9 100644
--- a/libs/common/src/lib/interfaces/responses/asset-profile-response.interface.ts
+++ b/libs/common/src/lib/interfaces/responses/asset-profile-response.interface.ts
@@ -1,4 +1,4 @@
-import { AssetProfileSplit, MarketData } from '@prisma/client';
+import type { AssetProfileSplit, MarketData } from '@ghostfolio/prisma/browser';
import { EnhancedAssetProfile } from '../enhanced-asset-profile.interface';
diff --git a/libs/common/src/lib/interfaces/responses/data-provider-ghostfolio-asset-profile-response.interface.ts b/libs/common/src/lib/interfaces/responses/data-provider-ghostfolio-asset-profile-response.interface.ts
index 3ea635c6d2..41a9c513c0 100644
--- a/libs/common/src/lib/interfaces/responses/data-provider-ghostfolio-asset-profile-response.interface.ts
+++ b/libs/common/src/lib/interfaces/responses/data-provider-ghostfolio-asset-profile-response.interface.ts
@@ -1,3 +1,3 @@
-import { SymbolProfile } from '@prisma/client';
+import type { SymbolProfile } from '@ghostfolio/prisma/browser';
export interface DataProviderGhostfolioAssetProfileResponse extends Partial {}
diff --git a/libs/common/src/lib/interfaces/responses/data-provider-response.interface.ts b/libs/common/src/lib/interfaces/responses/data-provider-response.interface.ts
index ff152b1b2f..d46c6ff5e3 100644
--- a/libs/common/src/lib/interfaces/responses/data-provider-response.interface.ts
+++ b/libs/common/src/lib/interfaces/responses/data-provider-response.interface.ts
@@ -1,7 +1,6 @@
import { DataProviderInfo } from '@ghostfolio/common/interfaces';
import { MarketState } from '@ghostfolio/common/types';
-
-import { DataSource } from '@prisma/client';
+import { DataSource } from '@ghostfolio/prisma/enums';
export interface DataProviderHistoricalResponse {
marketPrice: number;
diff --git a/libs/common/src/lib/interfaces/responses/export-response.interface.ts b/libs/common/src/lib/interfaces/responses/export-response.interface.ts
index 4d49afebd9..6f643ec1a1 100644
--- a/libs/common/src/lib/interfaces/responses/export-response.interface.ts
+++ b/libs/common/src/lib/interfaces/responses/export-response.interface.ts
@@ -1,4 +1,10 @@
-import { Account, Order, Platform, SymbolProfile, Tag } from '@prisma/client';
+import type {
+ Account,
+ Order,
+ Platform,
+ SymbolProfile,
+ Tag
+} from '@ghostfolio/prisma/browser';
import { AccountBalance } from '../account-balance.interface';
import { AssetProfileIdentifier } from '../asset-profile-identifier.interface';
diff --git a/libs/common/src/lib/interfaces/responses/platforms-response.interface.ts b/libs/common/src/lib/interfaces/responses/platforms-response.interface.ts
index 552912d9d6..a6bfc4326c 100644
--- a/libs/common/src/lib/interfaces/responses/platforms-response.interface.ts
+++ b/libs/common/src/lib/interfaces/responses/platforms-response.interface.ts
@@ -1,4 +1,4 @@
-import { Platform } from '@prisma/client';
+import type { Platform } from '@ghostfolio/prisma/browser';
export interface PlatformsResponse {
platforms: Platform[];
diff --git a/libs/common/src/lib/interfaces/responses/portfolio-holding-response.interface.ts b/libs/common/src/lib/interfaces/responses/portfolio-holding-response.interface.ts
index 80eaa6458c..a3ab21705c 100644
--- a/libs/common/src/lib/interfaces/responses/portfolio-holding-response.interface.ts
+++ b/libs/common/src/lib/interfaces/responses/portfolio-holding-response.interface.ts
@@ -4,8 +4,7 @@ import {
EnhancedAssetProfile,
HistoricalDataItem
} from '@ghostfolio/common/interfaces';
-
-import { Tag } from '@prisma/client';
+import type { Tag } from '@ghostfolio/prisma/browser';
export interface PortfolioHoldingResponse {
activitiesCount: number;
diff --git a/libs/common/src/lib/interfaces/responses/public-portfolio-response.interface.ts b/libs/common/src/lib/interfaces/responses/public-portfolio-response.interface.ts
index 616dbac992..bf6e91bd9a 100644
--- a/libs/common/src/lib/interfaces/responses/public-portfolio-response.interface.ts
+++ b/libs/common/src/lib/interfaces/responses/public-portfolio-response.interface.ts
@@ -4,8 +4,7 @@ import {
PortfolioPosition
} from '@ghostfolio/common/interfaces';
import { Market } from '@ghostfolio/common/types';
-
-import { Order } from '@prisma/client';
+import type { Order } from '@ghostfolio/prisma/browser';
export interface PublicPortfolioResponse extends PublicPortfolioResponseV1 {
alias?: string;
diff --git a/libs/common/src/lib/interfaces/user-item.interface.ts b/libs/common/src/lib/interfaces/user-item.interface.ts
index 32230b69ef..b5ca9d9ce6 100644
--- a/libs/common/src/lib/interfaces/user-item.interface.ts
+++ b/libs/common/src/lib/interfaces/user-item.interface.ts
@@ -1,4 +1,4 @@
-import { Role } from '@prisma/client';
+import { Role } from '@ghostfolio/prisma/enums';
export interface UserItem {
accessToken?: string;
diff --git a/libs/common/src/lib/interfaces/user-settings.interface.ts b/libs/common/src/lib/interfaces/user-settings.interface.ts
index 65325a42f8..dc6fb5c369 100644
--- a/libs/common/src/lib/interfaces/user-settings.interface.ts
+++ b/libs/common/src/lib/interfaces/user-settings.interface.ts
@@ -6,8 +6,7 @@ import {
ViewMode
} from '@ghostfolio/common/types';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
-
-import { AssetClass } from '@prisma/client';
+import { AssetClass } from '@ghostfolio/prisma/enums';
export interface UserSettings {
annualInterestRate?: number;
diff --git a/libs/common/src/lib/interfaces/user.interface.ts b/libs/common/src/lib/interfaces/user.interface.ts
index b6493c814a..ff0f6646f5 100644
--- a/libs/common/src/lib/interfaces/user.interface.ts
+++ b/libs/common/src/lib/interfaces/user.interface.ts
@@ -1,7 +1,7 @@
import { SubscriptionType } from '@ghostfolio/common/enums';
import { AccountWithPlatform } from '@ghostfolio/common/types';
-
-import { Access, Tag, Type as ActivityType } from '@prisma/client';
+import type { Access, Tag } from '@ghostfolio/prisma/browser';
+import { Type as ActivityType } from '@ghostfolio/prisma/enums';
import { ReferralPartner } from './referral-partner.interface';
import { SubscriptionOffer } from './subscription-offer.interface';
diff --git a/libs/common/src/lib/models/portfolio-snapshot-holding.ts b/libs/common/src/lib/models/portfolio-snapshot-holding.ts
index 21d375fd4a..9b440b9782 100644
--- a/libs/common/src/lib/models/portfolio-snapshot-holding.ts
+++ b/libs/common/src/lib/models/portfolio-snapshot-holding.ts
@@ -3,8 +3,9 @@ import {
transformToMapOfBig
} from '@ghostfolio/common/class-transformer';
import { DateRange } from '@ghostfolio/common/types';
+import type { Tag } from '@ghostfolio/prisma/browser';
+import { DataSource } from '@ghostfolio/prisma/enums';
-import { DataSource, Tag } from '@prisma/client';
import { Big } from 'big.js';
import { Expose, Transform, Type } from 'class-transformer';
diff --git a/libs/common/src/lib/permissions.ts b/libs/common/src/lib/permissions.ts
index 753bdf131a..d2b23e871a 100644
--- a/libs/common/src/lib/permissions.ts
+++ b/libs/common/src/lib/permissions.ts
@@ -1,6 +1,5 @@
import { UserWithSettings } from '@ghostfolio/common/types';
-
-import { Role } from '@prisma/client';
+import { Role } from '@ghostfolio/prisma/enums';
export const permissions = {
accessAdminControl: 'accessAdminControl',
diff --git a/libs/common/src/lib/scopes.ts b/libs/common/src/lib/scopes.ts
index 1c8efab431..635530a9e2 100644
--- a/libs/common/src/lib/scopes.ts
+++ b/libs/common/src/lib/scopes.ts
@@ -1,6 +1,5 @@
import { AccessLevel } from '@ghostfolio/common/types';
-
-import { AccessType } from '@prisma/client';
+import { AccessType } from '@ghostfolio/prisma/enums';
/**
* Scopes describe what a grantee may do on behalf of the granting user. They
diff --git a/libs/common/src/lib/types/access-with-grantee-user.type.ts b/libs/common/src/lib/types/access-with-grantee-user.type.ts
index 2fc2488eec..ea04986979 100644
--- a/libs/common/src/lib/types/access-with-grantee-user.type.ts
+++ b/libs/common/src/lib/types/access-with-grantee-user.type.ts
@@ -1,3 +1,3 @@
-import { Access, User } from '@prisma/client';
+import type { Access, User } from '@ghostfolio/prisma/browser';
export type AccessWithGranteeUser = Access & { granteeUser?: User | null };
diff --git a/libs/common/src/lib/types/account-with-balance.type.ts b/libs/common/src/lib/types/account-with-balance.type.ts
index 72732f3a6d..46e0e16548 100644
--- a/libs/common/src/lib/types/account-with-balance.type.ts
+++ b/libs/common/src/lib/types/account-with-balance.type.ts
@@ -1,4 +1,4 @@
-import { Account as AccountModel } from '@prisma/client';
+import type { Account as AccountModel } from '@ghostfolio/prisma/browser';
export type AccountWithBalance = AccountModel & {
balance: number;
diff --git a/libs/common/src/lib/types/account-with-platform.type.ts b/libs/common/src/lib/types/account-with-platform.type.ts
index 640245e3bc..cccec2e522 100644
--- a/libs/common/src/lib/types/account-with-platform.type.ts
+++ b/libs/common/src/lib/types/account-with-platform.type.ts
@@ -1,4 +1,4 @@
-import { Account, Platform, Tag } from '@prisma/client';
+import type { Account, Platform, Tag } from '@ghostfolio/prisma/browser';
export type AccountWithPlatform = Account & {
platform?: Platform;
diff --git a/libs/common/src/lib/types/account-with-value.type.ts b/libs/common/src/lib/types/account-with-value.type.ts
index 0d43c612f7..0fb9a58e2b 100644
--- a/libs/common/src/lib/types/account-with-value.type.ts
+++ b/libs/common/src/lib/types/account-with-value.type.ts
@@ -1,4 +1,4 @@
-import { Platform, Tag } from '@prisma/client';
+import type { Platform, Tag } from '@ghostfolio/prisma/browser';
import { AccountWithBalance } from './account-with-balance.type';
diff --git a/libs/common/src/lib/types/order-with-account.type.ts b/libs/common/src/lib/types/order-with-account.type.ts
index b3ae4a990a..cff844707f 100644
--- a/libs/common/src/lib/types/order-with-account.type.ts
+++ b/libs/common/src/lib/types/order-with-account.type.ts
@@ -1,4 +1,4 @@
-import { Order, SymbolProfile, Tag } from '@prisma/client';
+import type { Order, SymbolProfile, Tag } from '@ghostfolio/prisma/browser';
import { AccountWithPlatform } from './account-with-platform.type';
diff --git a/libs/common/src/lib/types/tag-with-account-and-activity-count.type.ts b/libs/common/src/lib/types/tag-with-account-and-activity-count.type.ts
index 662e0f5ce9..36ef4a56e4 100644
--- a/libs/common/src/lib/types/tag-with-account-and-activity-count.type.ts
+++ b/libs/common/src/lib/types/tag-with-account-and-activity-count.type.ts
@@ -1,4 +1,4 @@
-import { Tag } from '@prisma/client';
+import type { Tag } from '@ghostfolio/prisma/browser';
export type TagWithAccountAndActivityCount = Tag & {
accountCount: number;
diff --git a/libs/common/src/lib/types/user-with-settings.type.ts b/libs/common/src/lib/types/user-with-settings.type.ts
index 4c4a85a45c..24be244b39 100644
--- a/libs/common/src/lib/types/user-with-settings.type.ts
+++ b/libs/common/src/lib/types/user-with-settings.type.ts
@@ -1,7 +1,6 @@
import { SubscriptionType } from '@ghostfolio/common/enums';
import { SubscriptionOffer, UserSettings } from '@ghostfolio/common/interfaces';
-
-import { Account, Settings, User } from '@prisma/client';
+import type { Account, Settings, User } from '@ghostfolio/prisma/browser';
// TODO: Compare with User interface
export type UserWithSettings = User & {
diff --git a/libs/ui/src/lib/activities-table/activities-table.component.ts b/libs/ui/src/lib/activities-table/activities-table.component.ts
index 4646606577..56a2c6abec 100644
--- a/libs/ui/src/lib/activities-table/activities-table.component.ts
+++ b/libs/ui/src/lib/activities-table/activities-table.component.ts
@@ -6,6 +6,7 @@ import {
AssetProfileIdentifier
} from '@ghostfolio/common/interfaces';
import { internalRoutes } from '@ghostfolio/common/routes/routes';
+import { Type as ActivityType } from '@ghostfolio/prisma/enums';
import { translate } from '@ghostfolio/ui/i18n';
import { NotificationService } from '@ghostfolio/ui/notifications';
@@ -48,7 +49,6 @@ import { MatTableDataSource, MatTableModule } from '@angular/material/table';
import { MatTooltipModule } from '@angular/material/tooltip';
import { RouterModule } from '@angular/router';
import { IonIcon } from '@ionic/angular/standalone';
-import { Type as ActivityType } from '@prisma/client';
import { isUUID } from 'class-validator';
import { addIcons } from 'ionicons';
import {
diff --git a/libs/ui/src/lib/activity-type/activity-type.component.ts b/libs/ui/src/lib/activity-type/activity-type.component.ts
index 7bd0e6975d..acc7a27e25 100644
--- a/libs/ui/src/lib/activity-type/activity-type.component.ts
+++ b/libs/ui/src/lib/activity-type/activity-type.component.ts
@@ -1,3 +1,5 @@
+import { Type as ActivityType } from '@ghostfolio/prisma/enums';
+
import {
CUSTOM_ELEMENTS_SCHEMA,
ChangeDetectionStrategy,
@@ -6,7 +8,6 @@ import {
OnChanges
} from '@angular/core';
import { IonIcon } from '@ionic/angular/standalone';
-import { Type as ActivityType } from '@prisma/client';
import { addIcons } from 'ionicons';
import {
addCircleOutline,
diff --git a/libs/ui/src/lib/benchmark/benchmark-detail-dialog/interfaces/interfaces.ts b/libs/ui/src/lib/benchmark/benchmark-detail-dialog/interfaces/interfaces.ts
index b01403284b..80d09148c9 100644
--- a/libs/ui/src/lib/benchmark/benchmark-detail-dialog/interfaces/interfaces.ts
+++ b/libs/ui/src/lib/benchmark/benchmark-detail-dialog/interfaces/interfaces.ts
@@ -1,6 +1,5 @@
import { ColorScheme } from '@ghostfolio/common/types';
-
-import { DataSource } from '@prisma/client';
+import { DataSource } from '@ghostfolio/prisma/enums';
export interface BenchmarkDetailDialogParams {
colorScheme?: ColorScheme;
diff --git a/libs/ui/src/lib/entity-logo/entity-logo.component.ts b/libs/ui/src/lib/entity-logo/entity-logo.component.ts
index 8a788e0c80..989a67214e 100644
--- a/libs/ui/src/lib/entity-logo/entity-logo.component.ts
+++ b/libs/ui/src/lib/entity-logo/entity-logo.component.ts
@@ -1,4 +1,5 @@
import { getEmojiFlag } from '@ghostfolio/common/helper';
+import { DataSource } from '@ghostfolio/prisma/enums';
import { EntityLogoImageSourceService } from '@ghostfolio/ui/entity-logo/entity-logo-image-source.service';
import {
@@ -8,7 +9,6 @@ import {
Input,
OnChanges
} from '@angular/core';
-import { DataSource } from '@prisma/client';
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
diff --git a/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/interfaces/interfaces.ts b/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/interfaces/interfaces.ts
index edb9a852f5..fcc98c271e 100644
--- a/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/interfaces/interfaces.ts
+++ b/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/interfaces/interfaces.ts
@@ -1,6 +1,5 @@
import { User } from '@ghostfolio/common/interfaces';
-
-import { DataSource } from '@prisma/client';
+import { DataSource } from '@ghostfolio/prisma/enums';
export interface HistoricalMarketDataEditorDialogParams {
currency: string;
diff --git a/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts b/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts
index aa6177cff9..401fd84523 100644
--- a/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts
+++ b/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts
@@ -5,6 +5,8 @@ import {
getLocale
} from '@ghostfolio/common/helper';
import { LineChartItem, User } from '@ghostfolio/common/interfaces';
+import type { MarketData } from '@ghostfolio/prisma/browser';
+import { DataSource } from '@ghostfolio/prisma/enums';
import { DataService } from '@ghostfolio/ui/services';
import { CommonModule } from '@angular/common';
@@ -28,7 +30,6 @@ import { MatButtonModule } from '@angular/material/button';
import { MatDialog } from '@angular/material/dialog';
import { MatInputModule } from '@angular/material/input';
import { MatSnackBar } from '@angular/material/snack-bar';
-import { DataSource, MarketData } from '@prisma/client';
import {
addDays,
addMonths,
diff --git a/libs/ui/src/lib/mocks/entity-logo-image-source.service.mock.ts b/libs/ui/src/lib/mocks/entity-logo-image-source.service.mock.ts
index 766a4178b0..4160485bce 100644
--- a/libs/ui/src/lib/mocks/entity-logo-image-source.service.mock.ts
+++ b/libs/ui/src/lib/mocks/entity-logo-image-source.service.mock.ts
@@ -1,6 +1,5 @@
import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces';
-
-import { DataSource } from '@prisma/client';
+import { DataSource } from '@ghostfolio/prisma/enums';
// Resolves like a logo of an unknown entity, but fails to load
const UNAVAILABLE_LOGO_URL = 'data:image/png;base64,unavailable';
diff --git a/libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.util.ts b/libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.util.ts
index 193624c768..163029258a 100644
--- a/libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.util.ts
+++ b/libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.util.ts
@@ -1,8 +1,8 @@
import { TAG_ID_EXCLUDE_FROM_ANALYSIS } from '@ghostfolio/common/config';
import { getAssetProfileIdentifier } from '@ghostfolio/common/helper';
import { Filter, PortfolioPosition } from '@ghostfolio/common/interfaces';
-
-import { AssetClass, DataSource, Tag } from '@prisma/client';
+import type { Tag } from '@ghostfolio/prisma/browser';
+import { AssetClass, DataSource } from '@ghostfolio/prisma/enums';
import { translate } from '../i18n';
import { PortfolioFilterFormValue } from './interfaces';
diff --git a/libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts b/libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts
index bf2f595c26..e90c446b41 100644
--- a/libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts
+++ b/libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts
@@ -3,6 +3,7 @@ import { DEFAULT_COLOR_SCHEME, UNKNOWN_KEY } from '@ghostfolio/common/config';
import { getLocale, getSum, getTextColor } from '@ghostfolio/common/helper';
import { PortfolioPosition } from '@ghostfolio/common/interfaces';
import { ColorScheme } from '@ghostfolio/common/types';
+import { DataSource } from '@ghostfolio/prisma/enums';
import {
AfterViewInit,
@@ -15,7 +16,6 @@ import {
output,
viewChild
} from '@angular/core';
-import { DataSource } from '@prisma/client';
import { Big } from 'big.js';
import {
ArcElement,
diff --git a/libs/ui/src/lib/services/admin.service.ts b/libs/ui/src/lib/services/admin.service.ts
index aa048faaf2..aba02c9b3f 100644
--- a/libs/ui/src/lib/services/admin.service.ts
+++ b/libs/ui/src/lib/services/admin.service.ts
@@ -21,11 +21,15 @@ import {
EnhancedAssetProfile
} from '@ghostfolio/common/interfaces';
import { DateRange } from '@ghostfolio/common/types';
+import type {
+ AssetProfileSplit,
+ MarketData,
+ Platform
+} from '@ghostfolio/prisma/browser';
import { GF_ENVIRONMENT } from '@ghostfolio/ui/environment';
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
import { inject, Service } from '@angular/core';
-import { AssetProfileSplit, MarketData, Platform } from '@prisma/client';
import { JobStatus } from 'bull';
import { isNumber } from 'lodash';
diff --git a/libs/ui/src/lib/services/data.service.ts b/libs/ui/src/lib/services/data.service.ts
index e09632079c..9bbb0bf5fa 100644
--- a/libs/ui/src/lib/services/data.service.ts
+++ b/libs/ui/src/lib/services/data.service.ts
@@ -65,22 +65,22 @@ import type {
GroupBy,
TagWithAccountAndActivityCount
} from '@ghostfolio/common/types';
-import { translate } from '@ghostfolio/ui/i18n';
-
-import { HttpClient, HttpParams } from '@angular/common/http';
-import { inject, Service } from '@angular/core';
-import { SortDirection } from '@angular/material/sort';
-import { utc } from '@date-fns/utc';
-import {
+import type {
Account,
AccountBalance,
- DataSource,
MarketData,
Order,
SymbolProfile,
Tag,
User as UserModel
-} from '@prisma/client';
+} from '@ghostfolio/prisma/browser';
+import { DataSource } from '@ghostfolio/prisma/enums';
+import { translate } from '@ghostfolio/ui/i18n';
+
+import { HttpClient, HttpParams } from '@angular/common/http';
+import { inject, Service } from '@angular/core';
+import { SortDirection } from '@angular/material/sort';
+import { utc } from '@date-fns/utc';
import { format, parseISO } from 'date-fns';
import { cloneDeep, groupBy, isNumber } from 'lodash';
import { Observable } from 'rxjs';
diff --git a/libs/ui/src/lib/tags-selector/interfaces/interfaces.ts b/libs/ui/src/lib/tags-selector/interfaces/interfaces.ts
index fe9af9882c..616ffa2487 100644
--- a/libs/ui/src/lib/tags-selector/interfaces/interfaces.ts
+++ b/libs/ui/src/lib/tags-selector/interfaces/interfaces.ts
@@ -1,4 +1,4 @@
-import { Tag } from '@prisma/client';
+import type { Tag } from '@ghostfolio/prisma/browser';
export interface NewTag extends Omit {
id: undefined;
diff --git a/nx.json b/nx.json
index e0a41e9835..d42873bda8 100644
--- a/nx.json
+++ b/nx.json
@@ -125,7 +125,8 @@
"sharedGlobals": [
"{workspaceRoot}/angular.json",
"{workspaceRoot}/tsconfig.base.json",
- "{workspaceRoot}/nx.json"
+ "{workspaceRoot}/nx.json",
+ "{workspaceRoot}/prisma/schema.prisma"
],
"production": [
"default",
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index 8d874b4de6..d2181e13be 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -1,7 +1,12 @@
generator client {
- provider = "prisma-client-js"
- previewFeatures = []
binaryTargets = ["debian-openssl-3.0.x", "linux-arm64-openssl-3.0.x", "native"]
+ previewFeatures = []
+ provider = "prisma-client-js"
+}
+
+generator workspaceClient {
+ output = "./generated"
+ provider = "prisma-client"
}
datasource db {
diff --git a/tsconfig.base.json b/tsconfig.base.json
index a74d9910f3..f5771c4e9d 100644
--- a/tsconfig.base.json
+++ b/tsconfig.base.json
@@ -19,6 +19,8 @@
"@ghostfolio/api/*": ["./apps/api/src/*"],
"@ghostfolio/client/*": ["./apps/client/src/app/*"],
"@ghostfolio/common/*": ["./libs/common/src/lib/*"],
+ "@ghostfolio/prisma/browser": ["./prisma/generated/browser"],
+ "@ghostfolio/prisma/enums": ["./prisma/generated/enums"],
"@ghostfolio/ui/*": ["./libs/ui/src/lib/*"]
},
"noFallthroughCasesInSwitch": true,
From cce3f7bdc4a399a059ca60bba7c1c5a620c55a8c Mon Sep 17 00:00:00 2001
From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
Date: Tue, 29 Sep 2026 19:48:31 +0200
Subject: [PATCH 08/27] Feature/add currency suffix to emergency fund dialog
(#7969)
* Add currency suffix to emergency fund dialog
* Update changelog
---
CHANGELOG.md | 1 +
.../portfolio-summary/portfolio-summary.component.ts | 3 ++-
libs/ui/src/lib/notifications/interfaces/interfaces.ts | 1 +
libs/ui/src/lib/notifications/notification.service.ts | 3 ++-
.../lib/notifications/prompt-dialog/interfaces/interfaces.ts | 1 +
.../notifications/prompt-dialog/prompt-dialog.component.ts | 5 ++++-
.../src/lib/notifications/prompt-dialog/prompt-dialog.html | 3 +++
7 files changed, 14 insertions(+), 3 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d90f853cd0..1e3529e9e6 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
+- Extended the emergency fund dialog in the portfolio summary to include the currency suffix
- Migrated the client to the code generated by the new `prisma-client` generator of _Prisma_
## 3.75.0 - 2026-09-28
diff --git a/apps/client/src/app/components/portfolio-summary/portfolio-summary.component.ts b/apps/client/src/app/components/portfolio-summary/portfolio-summary.component.ts
index 23e32a0c9c..feedddacae 100644
--- a/apps/client/src/app/components/portfolio-summary/portfolio-summary.component.ts
+++ b/apps/client/src/app/components/portfolio-summary/portfolio-summary.component.ts
@@ -201,7 +201,8 @@ export class GfPortfolioSummaryComponent implements OnChanges {
},
confirmLabel: $localize`Save`,
defaultValue: this.summary.emergencyFund?.total?.toString() ?? '0',
- title: $localize`Please set the amount of your emergency fund.`
+ title: $localize`Please set the amount of your emergency fund.`,
+ valueSuffix: this.baseCurrency
});
}
diff --git a/libs/ui/src/lib/notifications/interfaces/interfaces.ts b/libs/ui/src/lib/notifications/interfaces/interfaces.ts
index dcea7d53c9..a9e065d83d 100644
--- a/libs/ui/src/lib/notifications/interfaces/interfaces.ts
+++ b/libs/ui/src/lib/notifications/interfaces/interfaces.ts
@@ -26,4 +26,5 @@ export interface PromptParams {
discardLabel?: string;
title: string;
valueLabel?: string;
+ valueSuffix?: string;
}
diff --git a/libs/ui/src/lib/notifications/notification.service.ts b/libs/ui/src/lib/notifications/notification.service.ts
index b0758b5763..4eb1cbd54d 100644
--- a/libs/ui/src/lib/notifications/notification.service.ts
+++ b/libs/ui/src/lib/notifications/notification.service.ts
@@ -87,7 +87,8 @@ export class NotificationService {
defaultValue: aParams.defaultValue,
discardLabel: aParams.discardLabel,
title: aParams.title,
- valueLabel: aParams.valueLabel
+ valueLabel: aParams.valueLabel,
+ valueSuffix: aParams.valueSuffix
});
return dialog.afterClosed().subscribe((result: string) => {
diff --git a/libs/ui/src/lib/notifications/prompt-dialog/interfaces/interfaces.ts b/libs/ui/src/lib/notifications/prompt-dialog/interfaces/interfaces.ts
index 607577dfa0..d3dcccf596 100644
--- a/libs/ui/src/lib/notifications/prompt-dialog/interfaces/interfaces.ts
+++ b/libs/ui/src/lib/notifications/prompt-dialog/interfaces/interfaces.ts
@@ -4,4 +4,5 @@ export interface PromptDialogParams {
discardLabel: string;
title: string;
valueLabel?: string;
+ valueSuffix?: string;
}
diff --git a/libs/ui/src/lib/notifications/prompt-dialog/prompt-dialog.component.ts b/libs/ui/src/lib/notifications/prompt-dialog/prompt-dialog.component.ts
index e9750c3f52..e74056401c 100644
--- a/libs/ui/src/lib/notifications/prompt-dialog/prompt-dialog.component.ts
+++ b/libs/ui/src/lib/notifications/prompt-dialog/prompt-dialog.component.ts
@@ -26,6 +26,7 @@ export class GfPromptDialogComponent {
public formControl = new FormControl('');
public title: string;
public valueLabel?: string;
+ public valueSuffix?: string;
protected readonly dialogRef =
inject>(MatDialogRef);
@@ -35,7 +36,8 @@ export class GfPromptDialogComponent {
defaultValue,
discardLabel,
title,
- valueLabel
+ valueLabel,
+ valueSuffix
}: PromptDialogParams) {
this.confirmLabel = confirmLabel;
this.defaultValue = defaultValue;
@@ -43,5 +45,6 @@ export class GfPromptDialogComponent {
this.formControl.setValue(defaultValue ?? null);
this.title = title;
this.valueLabel = valueLabel;
+ this.valueSuffix = valueSuffix;
}
}
diff --git a/libs/ui/src/lib/notifications/prompt-dialog/prompt-dialog.html b/libs/ui/src/lib/notifications/prompt-dialog/prompt-dialog.html
index d73c8dabb5..0b39f22e17 100644
--- a/libs/ui/src/lib/notifications/prompt-dialog/prompt-dialog.html
+++ b/libs/ui/src/lib/notifications/prompt-dialog/prompt-dialog.html
@@ -8,6 +8,9 @@
{{ valueLabel }}
}
+ @if (valueSuffix) {
+ {{ valueSuffix }}
+ }
From cdeff7e5884026df84e706d35412ca86af13f874 Mon Sep 17 00:00:00 2001
From: Kenrick Tandrian <60643640+KenTandrian@users.noreply.github.com>
Date: Wed, 30 Sep 2026 01:12:24 +0700
Subject: [PATCH 09/27] Task/widen input types in investment chart component
(#7966)
Widen input types
---
.../investment-chart/investment-chart.component.ts | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/apps/client/src/app/components/investment-chart/investment-chart.component.ts b/apps/client/src/app/components/investment-chart/investment-chart.component.ts
index 43ac5ff99b..8250133eb2 100644
--- a/apps/client/src/app/components/investment-chart/investment-chart.component.ts
+++ b/apps/client/src/app/components/investment-chart/investment-chart.component.ts
@@ -56,13 +56,13 @@ export class GfInvestmentChartComponent implements OnChanges, OnDestroy {
@Input() public benchmarkDataItems: InvestmentItem[] = [];
@Input() public benchmarkDataLabel = '';
@Input() public colorScheme: ColorScheme;
- @Input() public currency: string;
+ @Input() public currency?: string;
@Input() public groupBy: GroupBy;
@Input() public historicalDataItems: LineChartItem[] = [];
- @Input() public isInPercentage = false;
+ @Input() public isInPercentage?: boolean = false;
@Input() public isLoading = false;
- @Input() public locale = getLocale();
- @Input() public savingsRate = 0;
+ @Input() public locale?: string = getLocale();
+ @Input() public savingsRate?: number = 0;
private readonly chartCanvas =
viewChild.required>('chartCanvas');
From 720ad531a1cce98590867e853c1c00c91b8d86c7 Mon Sep 17 00:00:00 2001
From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
Date: Tue, 29 Sep 2026 20:25:50 +0200
Subject: [PATCH 10/27] Bugfix/endless loading state of values (#7971)
* Fix value column of top holdings in Presenter View
* Fix projected sustainable retirement income for projected total amount of zero
* Fix emergency fund in portfolio summary for redacted values
* Update changelog
---
CHANGELOG.md | 6 ++++++
.../portfolio-summary.component.html | 4 +++-
.../portfolio/allocations/allocations-page.html | 1 +
.../pages/portfolio/fire/fire-page.component.ts | 4 +++-
.../lib/top-holdings/top-holdings.component.ts | 15 ++++++++++-----
5 files changed, 23 insertions(+), 7 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1e3529e9e6..998d9fe239 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,6 +16,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Extended the emergency fund dialog in the portfolio summary to include the currency suffix
- Migrated the client to the code generated by the new `prisma-client` generator of _Prisma_
+### Fixed
+
+- Fixed the value column of the top holdings 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 emergency fund in the portfolio summary for redacted values
+
## 3.75.0 - 2026-09-28
### Changed
diff --git a/apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html b/apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html
index 2130493f03..0a0beba910 100644
--- a/apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html
+++ b/apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html
@@ -486,7 +486,9 @@
[locale]="locale"
[precision]="precision"
[unit]="displayedCurrency"
- [value]="summary?.emergencyFund?.total"
+ [value]="
+ summary?.emergencyFund === null ? null : summary?.emergencyFund?.total
+ "
/>
diff --git a/apps/client/src/app/pages/portfolio/allocations/allocations-page.html b/apps/client/src/app/pages/portfolio/allocations/allocations-page.html
index adb4f188d7..79f5e4ecbf 100644
--- a/apps/client/src/app/pages/portfolio/allocations/allocations-page.html
+++ b/apps/client/src/app/pages/portfolio/allocations/allocations-page.html
@@ -341,6 +341,7 @@
();
- public displayedColumns: string[] = [
- 'name',
- 'valueInBaseCurrency',
- 'allocationInPercentage'
- ];
+ public displayedColumns: string[] = [];
public isLoading = true;
public ngOnChanges() {
+ this.displayedColumns = ['name'];
+
+ if (this.hasPermissionToShowValues) {
+ this.displayedColumns.push('valueInBaseCurrency');
+ }
+
+ this.displayedColumns.push('allocationInPercentage');
+
this.isLoading = true;
this.dataSource = new MatTableDataSource(this.topHoldings);
From 2db2a471147598b1e583264284e04268530a3fc9 Mon Sep 17 00:00:00 2001
From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
Date: Tue, 29 Sep 2026 20:51:37 +0200
Subject: [PATCH 11/27] Bugfix/color scheme of investment chart (#7972)
* Fix color scheme of investment chart component
* Update changelog
---
CHANGELOG.md | 1 +
.../account-detail-dialog.html | 1 +
.../investment-chart.component.ts | 24 ++++++++++++-------
.../portfolio/analysis/analysis-page.html | 3 +++
4 files changed, 20 insertions(+), 9 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 998d9fe239..7c55df458c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
+- Fixed the colors of the investment chart component (dark mode)
- Fixed the value column of the top holdings 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 emergency fund in the portfolio summary for redacted values
diff --git a/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html b/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html
index a87179ecb9..5a86e4aa9b 100644
--- a/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html
+++ b/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html
@@ -23,6 +23,7 @@
(
this.chartCanvas().nativeElement,
{
data: chartData,
options: {
animation: false,
- elements: getChartElementsOptions(this.colorScheme),
+ elements: getChartElementsOptions(colorScheme),
interaction: { intersect: false, mode: 'index' },
maintainAspectRatio: true,
plugins: {
@@ -204,7 +210,7 @@ export class GfInvestmentChartComponent implements OnChanges, OnDestroy {
value: this.savingsRate
}
: undefined,
- yAxis: getZeroLineAnnotation(this.colorScheme)
+ yAxis: getZeroLineAnnotation(colorScheme)
}
},
legend: {
@@ -212,18 +218,18 @@ export class GfInvestmentChartComponent implements OnChanges, OnDestroy {
},
tooltip: this.getTooltipPluginConfiguration(),
verticalHoverLine: {
- color: getChartBorderColor(this.colorScheme)
+ color: getChartBorderColor(colorScheme)
}
},
responsive: true,
scales: {
x: getTimeAxisOptions({
+ colorScheme,
borderWidth: this.groupBy ? 0 : 1,
- colorScheme: this.colorScheme,
locale: this.locale
}),
y: getValueAxisOptions({
- colorScheme: this.colorScheme,
+ colorScheme,
display: !this.isInPercentage,
tickCallback: (tickValue) => {
return transformTickToAbbreviation(Number(tickValue));
@@ -232,7 +238,7 @@ export class GfInvestmentChartComponent implements OnChanges, OnDestroy {
}
},
plugins: [
- getVerticalHoverLinePlugin(this.chartCanvas(), this.colorScheme)
+ getVerticalHoverLinePlugin(this.chartCanvas(), colorScheme)
],
type: this.groupBy ? 'bar' : 'line'
}
@@ -245,7 +251,7 @@ export class GfInvestmentChartComponent implements OnChanges, OnDestroy {
TooltipOptions<'bar' | 'line'>
> {
return getTimeSeriesTooltipOptions<'bar' | 'line'>({
- colorScheme: this.colorScheme,
+ colorScheme: this.colorScheme ?? DEFAULT_COLOR_SCHEME,
currency: this.isInPercentage ? undefined : this.currency,
groupBy: this.groupBy,
locale: this.isInPercentage ? undefined : this.locale,
diff --git a/apps/client/src/app/pages/portfolio/analysis/analysis-page.html b/apps/client/src/app/pages/portfolio/analysis/analysis-page.html
index 47cee7af6e..2756076b7a 100644
--- a/apps/client/src/app/pages/portfolio/analysis/analysis-page.html
+++ b/apps/client/src/app/pages/portfolio/analysis/analysis-page.html
@@ -443,6 +443,7 @@
class="h-100"
[benchmarkDataItems]="investments"
[benchmarkDataLabel]="portfolioEvolutionDataLabel"
+ [colorScheme]="user?.settings?.colorScheme"
[currency]="user?.settings?.baseCurrency"
[historicalDataItems]="performanceDataItems"
[isInPercentage]="showValuesInPercentage()"
@@ -499,6 +500,7 @@
class="h-100"
[benchmarkDataItems]="investmentsByGroup"
[benchmarkDataLabel]="investmentTimelineDataLabel"
+ [colorScheme]="user?.settings?.colorScheme"
[currency]="user?.settings?.baseCurrency"
[groupBy]="mode()"
[isInPercentage]="showValuesInPercentage()"
@@ -534,6 +536,7 @@
class="h-100"
[benchmarkDataItems]="dividendsByGroup"
[benchmarkDataLabel]="dividendTimelineDataLabel"
+ [colorScheme]="user?.settings?.colorScheme"
[currency]="user?.settings?.baseCurrency"
[groupBy]="mode()"
[isInPercentage]="showValuesInPercentage()"
From c13a0c18fb9c221adc4fed02d3ba32b274c8dc80 Mon Sep 17 00:00:00 2001
From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
Date: Tue, 29 Sep 2026 21:53:38 +0200
Subject: [PATCH 12/27] Bugfix/redaction of values of ETF holdings (#7973)
* Fix redaction of values of ETF holdings
* Update changelog
---
CHANGELOG.md | 1 +
apps/api/src/helper/object.helper.spec.ts | 1050 ++++++++++++---------
libs/common/src/lib/config.ts | 2 +-
3 files changed, 582 insertions(+), 471 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7c55df458c..4bbf0a08dd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fixed the value column of the top holdings 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 emergency fund in the portfolio summary for redacted values
+- Fixed the redaction of `valueInBaseCurrency` in the holdings of ETFs in a restricted view
## 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 415851a6c6..ca487ed8f7 100644
--- a/apps/api/src/helper/object.helper.spec.ts
+++ b/apps/api/src/helper/object.helper.spec.ts
@@ -1,4 +1,5 @@
import { DEFAULT_REDACTED_PATHS } from '@ghostfolio/common/config';
+import { PortfolioDetails } from '@ghostfolio/common/interfaces';
import { query, redactPaths } from './object.helper';
@@ -133,6 +134,7 @@ describe('redactAttributes', () => {
tags: [],
allocationInPercentage: 0.044900865255793135,
assetClass: 'EQUITY',
+ assetProfile: { holdings: [] },
assetSubClass: 'STOCK',
countries: [
{
@@ -149,7 +151,6 @@ describe('redactAttributes', () => {
grossPerformancePercent: 0.3183066634822068,
grossPerformancePercentWithCurrencyEffect: 0.3183066634822068,
grossPerformanceWithCurrencyEffect: 2665.5,
- holdings: [],
investment: 0.060265768702233234,
name: 'Apple Inc',
netPerformance: 2664.5,
@@ -185,6 +186,7 @@ describe('redactAttributes', () => {
tags: [],
allocationInPercentage: 0.026912563036519527,
assetClass: 'EQUITY',
+ assetProfile: { holdings: [] },
assetSubClass: 'STOCK',
countries: [
{ code: 'DE', weight: 1, continent: 'Europe', name: 'Germany' }
@@ -196,7 +198,6 @@ describe('redactAttributes', () => {
grossPerformancePercent: 0.3719230057375532,
grossPerformancePercentWithCurrencyEffect: 0.2650716044872953,
grossPerformanceWithCurrencyEffect: 1386.429698978564,
- holdings: [],
investment: 0.03471025137190358,
name: 'Allianz SE',
netPerformance: 1789.1095737558583,
@@ -232,6 +233,7 @@ describe('redactAttributes', () => {
tags: [],
allocationInPercentage: 0.07646101417126275,
assetClass: 'EQUITY',
+ assetProfile: { holdings: [] },
assetSubClass: 'STOCK',
countries: [
{
@@ -248,7 +250,6 @@ describe('redactAttributes', () => {
grossPerformancePercent: 0.8594552890963852,
grossPerformancePercentWithCurrencyEffect: 0.8594552890963852,
grossPerformanceWithCurrencyEffect: 8689.05,
- holdings: [],
investment: 0.07275900505029173,
name: 'Amazon.com, Inc.',
netPerformance: 8608.26,
@@ -290,6 +291,7 @@ describe('redactAttributes', () => {
],
allocationInPercentage: 0.15042891393226654,
assetClass: 'LIQUIDITY',
+ assetProfile: { holdings: [] },
assetSubClass: 'CRYPTOCURRENCY',
countries: [],
dataSource: 'COINGECKO',
@@ -299,7 +301,6 @@ describe('redactAttributes', () => {
grossPerformancePercent: 17.4925166352,
grossPerformancePercentWithCurrencyEffect: 17.4925166352,
grossPerformanceWithCurrencyEffect: 34985.0332704,
- holdings: [],
investment: 0.014393543993846005,
name: 'Bitcoin',
netPerformance: 34955.1332704,
@@ -341,6 +342,7 @@ describe('redactAttributes', () => {
],
allocationInPercentage: 0.009076749759365777,
assetClass: 'FIXED_INCOME',
+ assetProfile: { holdings: [] },
assetSubClass: 'BOND',
countries: [],
dataSource: 'MANUAL',
@@ -350,7 +352,6 @@ describe('redactAttributes', () => {
grossPerformancePercent: 0,
grossPerformancePercentWithCurrencyEffect: -0.06153834320225245,
grossPerformanceWithCurrencyEffect: -125.68932723700505,
- holdings: [],
investment: 0.016060638243523776,
name: 'Bondora Go & Grow',
netPerformance: 0,
@@ -392,6 +393,7 @@ describe('redactAttributes', () => {
],
allocationInPercentage: 0.09095764645669335,
assetClass: 'EQUITY',
+ assetProfile: { holdings: [] },
assetSubClass: 'ETF',
countries: [
{
@@ -474,7 +476,6 @@ describe('redactAttributes', () => {
grossPerformancePercent: 0.27579517683678895,
grossPerformancePercentWithCurrencyEffect: 0.458553421589667,
grossPerformanceWithCurrencyEffect: 5322.44900391902,
- holdings: [],
investment: 0.13551383737034509,
name: 'frankly Extreme 95 Index',
netPerformance: 3533.389614611676,
@@ -510,6 +511,7 @@ describe('redactAttributes', () => {
tags: [],
allocationInPercentage: 0.05222646409742627,
assetClass: 'EQUITY',
+ assetProfile: { holdings: [] },
assetSubClass: 'STOCK',
countries: [
{
@@ -526,7 +528,6 @@ describe('redactAttributes', () => {
grossPerformancePercent: 0.7865431171216295,
grossPerformancePercentWithCurrencyEffect: 0.7865431171216295,
grossPerformanceWithCurrencyEffect: 5653.2,
- holdings: [],
investment: 0.051726079050684395,
name: 'Microsoft Corporation',
netPerformance: 5653.2,
@@ -562,6 +563,7 @@ describe('redactAttributes', () => {
tags: [],
allocationInPercentage: 0.1589050142378352,
assetClass: 'EQUITY',
+ assetProfile: { holdings: [] },
assetSubClass: 'STOCK',
countries: [
{
@@ -578,7 +580,6 @@ describe('redactAttributes', () => {
grossPerformancePercent: 17.184314638161936,
grossPerformancePercentWithCurrencyEffect: 17.184314638161936,
grossPerformanceWithCurrencyEffect: 36920.500000005,
- holdings: [],
investment: 0.01546226463535309,
name: 'Tesla, Inc.',
netPerformance: 36890.500000005,
@@ -614,6 +615,85 @@ describe('redactAttributes', () => {
tags: [],
allocationInPercentage: 0.057358979326040366,
assetClass: 'EQUITY',
+ assetProfile: {
+ holdings: [
+ {
+ allocationInPercentage: 0.06099941636982121,
+ name: 'APPLE INC',
+ valueInBaseCurrency: 860.2442693554036
+ },
+ {
+ allocationInPercentage: 0.05862464529372787,
+ name: 'MICROSOFT CORP',
+ valueInBaseCurrency: 826.7540602547973
+ },
+ {
+ allocationInPercentage: 0.05156070760128074,
+ name: 'NVIDIA CORP',
+ valueInBaseCurrency: 727.1348789470617
+ },
+ {
+ allocationInPercentage: 0.03301535551128066,
+ name: 'AMAZON COM INC',
+ valueInBaseCurrency: 465.5990510978355
+ },
+ {
+ allocationInPercentage: 0.01962204914568647,
+ name: 'FACEBOOK CLASS A INC',
+ valueInBaseCurrency: 276.71994807704345
+ },
+ {
+ allocationInPercentage: 0.01902835637666313,
+ name: 'ALPHABET INC CLASS A',
+ valueInBaseCurrency: 268.34739580189176
+ },
+ {
+ allocationInPercentage: 0.01555676306627245,
+ name: 'ALPHABET INC CLASS C',
+ valueInBaseCurrency: 219.3892511421072
+ },
+ {
+ allocationInPercentage: 0.01463100485016827,
+ name: 'BERKSHIRE HATHAWAY INC CLASS B',
+ valueInBaseCurrency: 206.33374589949804
+ },
+ {
+ allocationInPercentage: 0.01403731208114493,
+ name: 'BROADCOM INC',
+ valueInBaseCurrency: 197.96119362434638
+ },
+ {
+ allocationInPercentage: 0.01297067761476403,
+ name: 'ELI LILLY',
+ valueInBaseCurrency: 182.91898106220972
+ },
+ {
+ allocationInPercentage: 0.0118637927911612,
+ name: 'TESLA INC',
+ valueInBaseCurrency: 167.30913783735082
+ },
+ {
+ allocationInPercentage: 0.01152166475477487,
+ name: 'JPMORGAN CHASE & CO',
+ valueInBaseCurrency: 162.48427720421262
+ },
+ {
+ allocationInPercentage: 0.0100324015375638,
+ name: 'EXXON MOBIL CORP',
+ valueInBaseCurrency: 141.4819426834935
+ },
+ {
+ allocationInPercentage: 0.01000221376964736,
+ name: 'UNITEDHEALTH GROUP INC',
+ valueInBaseCurrency: 141.0562196864519
+ },
+ {
+ allocationInPercentage: 0.007818631890358146,
+ name: 'VISA INC CLASS A',
+ valueInBaseCurrency: 110.26225623377576
+ }
+ ]
+ },
assetSubClass: 'ETF',
countries: [
{
@@ -660,83 +740,6 @@ describe('redactAttributes', () => {
grossPerformancePercent: 0.8832083851170418,
grossPerformancePercentWithCurrencyEffect: 0.8832083851170418,
grossPerformanceWithCurrencyEffect: 5856.3,
- holdings: [
- {
- allocationInPercentage: 0.06099941636982121,
- name: 'APPLE INC',
- valueInBaseCurrency: 860.2442693554036
- },
- {
- allocationInPercentage: 0.05862464529372787,
- name: 'MICROSOFT CORP',
- valueInBaseCurrency: 826.7540602547973
- },
- {
- allocationInPercentage: 0.05156070760128074,
- name: 'NVIDIA CORP',
- valueInBaseCurrency: 727.1348789470617
- },
- {
- allocationInPercentage: 0.03301535551128066,
- name: 'AMAZON COM INC',
- valueInBaseCurrency: 465.5990510978355
- },
- {
- allocationInPercentage: 0.01962204914568647,
- name: 'FACEBOOK CLASS A INC',
- valueInBaseCurrency: 276.71994807704345
- },
- {
- allocationInPercentage: 0.01902835637666313,
- name: 'ALPHABET INC CLASS A',
- valueInBaseCurrency: 268.34739580189176
- },
- {
- allocationInPercentage: 0.01555676306627245,
- name: 'ALPHABET INC CLASS C',
- valueInBaseCurrency: 219.3892511421072
- },
- {
- allocationInPercentage: 0.01463100485016827,
- name: 'BERKSHIRE HATHAWAY INC CLASS B',
- valueInBaseCurrency: 206.33374589949804
- },
- {
- allocationInPercentage: 0.01403731208114493,
- name: 'BROADCOM INC',
- valueInBaseCurrency: 197.96119362434638
- },
- {
- allocationInPercentage: 0.01297067761476403,
- name: 'ELI LILLY',
- valueInBaseCurrency: 182.91898106220972
- },
- {
- allocationInPercentage: 0.0118637927911612,
- name: 'TESLA INC',
- valueInBaseCurrency: 167.30913783735082
- },
- {
- allocationInPercentage: 0.01152166475477487,
- name: 'JPMORGAN CHASE & CO',
- valueInBaseCurrency: 162.48427720421262
- },
- {
- allocationInPercentage: 0.0100324015375638,
- name: 'EXXON MOBIL CORP',
- valueInBaseCurrency: 141.4819426834935
- },
- {
- allocationInPercentage: 0.01000221376964736,
- name: 'UNITEDHEALTH GROUP INC',
- valueInBaseCurrency: 141.0562196864519
- },
- {
- allocationInPercentage: 0.007818631890358146,
- name: 'VISA INC CLASS A',
- valueInBaseCurrency: 110.26225623377576
- }
- ],
investment: 0.05934602124102648,
name: 'Vanguard Total Stock Market Index Fund ETF Shares',
netPerformance: 5756.8,
@@ -786,6 +789,85 @@ describe('redactAttributes', () => {
tags: [],
allocationInPercentage: 0.09386983901959013,
assetClass: 'EQUITY',
+ assetProfile: {
+ holdings: [
+ {
+ allocationInPercentage: 0.042520261085,
+ name: 'APPLE INC',
+ valueInBaseCurrency: 981.3336460398625
+ },
+ {
+ allocationInPercentage: 0.037017038038,
+ name: 'MICROSOFT CORP',
+ valueInBaseCurrency: 854.3236559815404
+ },
+ {
+ allocationInPercentage: 0.018861883836,
+ name: 'AMAZON COM INC',
+ valueInBaseCurrency: 435.31720557783655
+ },
+ {
+ allocationInPercentage: 0.017806548325,
+ name: 'NVIDIA CORP',
+ valueInBaseCurrency: 410.9609053487602
+ },
+ {
+ allocationInPercentage: 0.012188534864,
+ name: 'ALPHABET INC CLASS A',
+ valueInBaseCurrency: 281.3016442693628
+ },
+ {
+ allocationInPercentage: 0.010831709166,
+ name: 'ALPHABET INC CLASS C',
+ valueInBaseCurrency: 249.98719145833246
+ },
+ {
+ allocationInPercentage: 0.010813551981,
+ name: 'TESLA INC',
+ valueInBaseCurrency: 249.56813813873381
+ },
+ {
+ allocationInPercentage: 0.009934819182,
+ name: 'FACEBOOK CLASS A INC',
+ valueInBaseCurrency: 229.28768737165962
+ },
+ {
+ allocationInPercentage: 0.007403227621000001,
+ name: 'BERKSHIRE HATHAWAY INC CLASS B',
+ valueInBaseCurrency: 170.8605772494153
+ },
+ {
+ allocationInPercentage: 0.007076883908,
+ name: 'ELI LILLY',
+ valueInBaseCurrency: 163.32882514892185
+ },
+ {
+ allocationInPercentage: 0.006844271583,
+ name: 'EXXON MOBIL CORP',
+ valueInBaseCurrency: 157.96031857861325
+ },
+ {
+ allocationInPercentage: 0.006718061670999999,
+ name: 'UNITEDHEALTH GROUP INC',
+ valueInBaseCurrency: 155.0474946695187
+ },
+ {
+ allocationInPercentage: 0.006456949621,
+ name: 'JPMORGAN CHASE & CO',
+ valueInBaseCurrency: 149.02123722158794
+ },
+ {
+ allocationInPercentage: 0.006293890054,
+ name: 'TAIWAN SEMICONDUCTOR MANUFACTURING',
+ valueInBaseCurrency: 145.25795272326576
+ },
+ {
+ allocationInPercentage: 0.00600392555,
+ name: 'VISA INC CLASS A',
+ valueInBaseCurrency: 138.56580369427397
+ }
+ ]
+ },
assetSubClass: 'ETF',
countries: [
{
@@ -1060,83 +1142,6 @@ describe('redactAttributes', () => {
grossPerformancePercent: 0.3683200415015591,
grossPerformancePercentWithCurrencyEffect: 0.5806366182968891,
grossPerformanceWithCurrencyEffect: 6402.248165662604,
- holdings: [
- {
- allocationInPercentage: 0.042520261085,
- name: 'APPLE INC',
- valueInBaseCurrency: 981.3336460398625
- },
- {
- allocationInPercentage: 0.037017038038,
- name: 'MICROSOFT CORP',
- valueInBaseCurrency: 854.3236559815404
- },
- {
- allocationInPercentage: 0.018861883836,
- name: 'AMAZON COM INC',
- valueInBaseCurrency: 435.31720557783655
- },
- {
- allocationInPercentage: 0.017806548325,
- name: 'NVIDIA CORP',
- valueInBaseCurrency: 410.9609053487602
- },
- {
- allocationInPercentage: 0.012188534864,
- name: 'ALPHABET INC CLASS A',
- valueInBaseCurrency: 281.3016442693628
- },
- {
- allocationInPercentage: 0.010831709166,
- name: 'ALPHABET INC CLASS C',
- valueInBaseCurrency: 249.98719145833246
- },
- {
- allocationInPercentage: 0.010813551981,
- name: 'TESLA INC',
- valueInBaseCurrency: 249.56813813873381
- },
- {
- allocationInPercentage: 0.009934819182,
- name: 'FACEBOOK CLASS A INC',
- valueInBaseCurrency: 229.28768737165962
- },
- {
- allocationInPercentage: 0.007403227621000001,
- name: 'BERKSHIRE HATHAWAY INC CLASS B',
- valueInBaseCurrency: 170.8605772494153
- },
- {
- allocationInPercentage: 0.007076883908,
- name: 'ELI LILLY',
- valueInBaseCurrency: 163.32882514892185
- },
- {
- allocationInPercentage: 0.006844271583,
- name: 'EXXON MOBIL CORP',
- valueInBaseCurrency: 157.96031857861325
- },
- {
- allocationInPercentage: 0.006718061670999999,
- name: 'UNITEDHEALTH GROUP INC',
- valueInBaseCurrency: 155.0474946695187
- },
- {
- allocationInPercentage: 0.006456949621,
- name: 'JPMORGAN CHASE & CO',
- valueInBaseCurrency: 149.02123722158794
- },
- {
- allocationInPercentage: 0.006293890054,
- name: 'TAIWAN SEMICONDUCTOR MANUFACTURING',
- valueInBaseCurrency: 145.25795272326576
- },
- {
- allocationInPercentage: 0.00600392555,
- name: 'VISA INC CLASS A',
- valueInBaseCurrency: 138.56580369427397
- }
- ],
investment: 0.13346122254229614,
name: 'Vanguard FTSE All-World UCITS ETF',
netPerformance: 4438.993935069568,
@@ -1194,6 +1199,80 @@ describe('redactAttributes', () => {
tags: [],
allocationInPercentage: 0.03598477442100562,
assetClass: 'EQUITY',
+ assetProfile: {
+ holdings: [
+ {
+ allocationInPercentage: 0.051778373,
+ name: 'APPLE INC',
+ valueInBaseCurrency: 458.1016731945994
+ },
+ {
+ allocationInPercentage: 0.0403267055,
+ name: 'MICROSOFT CORP',
+ valueInBaseCurrency: 356.78469974280296
+ },
+ {
+ allocationInPercentage: 0.0221895862,
+ name: 'AMAZON COM INC',
+ valueInBaseCurrency: 196.3191575315778
+ },
+ {
+ allocationInPercentage: 0.0208100035,
+ name: 'NVIDIA CORP',
+ valueInBaseCurrency: 184.1134989416425
+ },
+ {
+ allocationInPercentage: 0.0139820061,
+ name: 'ALPHABET INC CLASS A',
+ valueInBaseCurrency: 123.70377858390985
+ },
+ {
+ allocationInPercentage: 0.0126263246,
+ name: 'ALPHABET INC CLASS C',
+ valueInBaseCurrency: 111.70958240727516
+ },
+ {
+ allocationInPercentage: 0.0121596126,
+ name: 'TESLA INC',
+ valueInBaseCurrency: 107.58041542669048
+ },
+ {
+ allocationInPercentage: 0.0114079282,
+ name: 'FACEBOOK CLASS A INC',
+ valueInBaseCurrency: 100.92999631533141
+ },
+ {
+ allocationInPercentage: 0.0081570352,
+ name: 'BERKSHIRE HATHAWAY INC CLASS B',
+ valueInBaseCurrency: 72.16819024860523
+ },
+ {
+ allocationInPercentage: 0.0079471416,
+ name: 'EXXON MOBIL CORP',
+ valueInBaseCurrency: 70.31118695201964
+ },
+ {
+ allocationInPercentage: 0.0078190388,
+ name: 'ELI LILLY',
+ valueInBaseCurrency: 69.1778159397456
+ },
+ {
+ allocationInPercentage: 0.0077121293,
+ name: 'UNITEDHEALTH GROUP INC',
+ valueInBaseCurrency: 68.23194958681098
+ },
+ {
+ allocationInPercentage: 0.0074484861,
+ name: 'JPMORGAN CHASE & CO',
+ valueInBaseCurrency: 65.89940447098863
+ },
+ {
+ allocationInPercentage: 0.006978079,
+ name: 'VISA INC CLASS A',
+ valueInBaseCurrency: 61.73754562709217
+ }
+ ]
+ },
assetSubClass: 'ETF',
countries: [
{
@@ -1342,78 +1421,6 @@ describe('redactAttributes', () => {
grossPerformancePercent: 0.3474381850624522,
grossPerformancePercentWithCurrencyEffect: 0.28744846894552306,
grossPerformanceWithCurrencyEffect: 1975.348026988124,
- holdings: [
- {
- allocationInPercentage: 0.051778373,
- name: 'APPLE INC',
- valueInBaseCurrency: 458.1016731945994
- },
- {
- allocationInPercentage: 0.0403267055,
- name: 'MICROSOFT CORP',
- valueInBaseCurrency: 356.78469974280296
- },
- {
- allocationInPercentage: 0.0221895862,
- name: 'AMAZON COM INC',
- valueInBaseCurrency: 196.3191575315778
- },
- {
- allocationInPercentage: 0.0208100035,
- name: 'NVIDIA CORP',
- valueInBaseCurrency: 184.1134989416425
- },
- {
- allocationInPercentage: 0.0139820061,
- name: 'ALPHABET INC CLASS A',
- valueInBaseCurrency: 123.70377858390985
- },
- {
- allocationInPercentage: 0.0126263246,
- name: 'ALPHABET INC CLASS C',
- valueInBaseCurrency: 111.70958240727516
- },
- {
- allocationInPercentage: 0.0121596126,
- name: 'TESLA INC',
- valueInBaseCurrency: 107.58041542669048
- },
- {
- allocationInPercentage: 0.0114079282,
- name: 'FACEBOOK CLASS A INC',
- valueInBaseCurrency: 100.92999631533141
- },
- {
- allocationInPercentage: 0.0081570352,
- name: 'BERKSHIRE HATHAWAY INC CLASS B',
- valueInBaseCurrency: 72.16819024860523
- },
- {
- allocationInPercentage: 0.0079471416,
- name: 'EXXON MOBIL CORP',
- valueInBaseCurrency: 70.31118695201964
- },
- {
- allocationInPercentage: 0.0078190388,
- name: 'ELI LILLY',
- valueInBaseCurrency: 69.1778159397456
- },
- {
- allocationInPercentage: 0.0077121293,
- name: 'UNITEDHEALTH GROUP INC',
- valueInBaseCurrency: 68.23194958681098
- },
- {
- allocationInPercentage: 0.0074484861,
- name: 'JPMORGAN CHASE & CO',
- valueInBaseCurrency: 65.89940447098863
- },
- {
- allocationInPercentage: 0.006978079,
- name: 'VISA INC CLASS A',
- valueInBaseCurrency: 61.73754562709217
- }
- ],
investment: 0.04725441287200783,
name: 'Xtrackers MSCI World UCITS ETF 1C',
netPerformance: 2247.935728632002,
@@ -1454,6 +1461,7 @@ describe('redactAttributes', () => {
currency: 'USD',
allocationInPercentage: 0.20291717628620132,
assetClass: 'LIQUIDITY',
+ assetProfile: { holdings: [] },
assetSubClass: 'CASH',
countries: [],
dividend: 0,
@@ -1461,7 +1469,6 @@ describe('redactAttributes', () => {
grossPerformancePercent: 0,
grossPerformancePercentWithCurrencyEffect: 0,
grossPerformanceWithCurrencyEffect: 0,
- holdings: [],
investment: 0.35904695492648864,
marketPrice: 0,
name: 'USD',
@@ -1637,6 +1644,7 @@ describe('redactAttributes', () => {
tags: [],
allocationInPercentage: 0.044900865255793135,
assetClass: 'EQUITY',
+ assetProfile: { holdings: [] },
assetSubClass: 'STOCK',
countries: [
{
@@ -1653,7 +1661,6 @@ describe('redactAttributes', () => {
grossPerformancePercent: 0.3183066634822068,
grossPerformancePercentWithCurrencyEffect: 0.3183066634822068,
grossPerformanceWithCurrencyEffect: null,
- holdings: [],
investment: null,
name: 'Apple Inc',
netPerformance: null,
@@ -1689,6 +1696,7 @@ describe('redactAttributes', () => {
tags: [],
allocationInPercentage: 0.026912563036519527,
assetClass: 'EQUITY',
+ assetProfile: { holdings: [] },
assetSubClass: 'STOCK',
countries: [
{ code: 'DE', weight: 1, continent: 'Europe', name: 'Germany' }
@@ -1700,7 +1708,6 @@ describe('redactAttributes', () => {
grossPerformancePercent: 0.3719230057375532,
grossPerformancePercentWithCurrencyEffect: 0.2650716044872953,
grossPerformanceWithCurrencyEffect: null,
- holdings: [],
investment: null,
name: 'Allianz SE',
netPerformance: null,
@@ -1736,6 +1743,7 @@ describe('redactAttributes', () => {
tags: [],
allocationInPercentage: 0.07646101417126275,
assetClass: 'EQUITY',
+ assetProfile: { holdings: [] },
assetSubClass: 'STOCK',
countries: [
{
@@ -1752,7 +1760,6 @@ describe('redactAttributes', () => {
grossPerformancePercent: 0.8594552890963852,
grossPerformancePercentWithCurrencyEffect: 0.8594552890963852,
grossPerformanceWithCurrencyEffect: null,
- holdings: [],
investment: null,
name: 'Amazon.com, Inc.',
netPerformance: null,
@@ -1794,6 +1801,7 @@ describe('redactAttributes', () => {
],
allocationInPercentage: 0.15042891393226654,
assetClass: 'LIQUIDITY',
+ assetProfile: { holdings: [] },
assetSubClass: 'CRYPTOCURRENCY',
countries: [],
dataSource: 'COINGECKO',
@@ -1803,7 +1811,6 @@ describe('redactAttributes', () => {
grossPerformancePercent: 17.4925166352,
grossPerformancePercentWithCurrencyEffect: 17.4925166352,
grossPerformanceWithCurrencyEffect: null,
- holdings: [],
investment: null,
name: 'Bitcoin',
netPerformance: null,
@@ -1845,6 +1852,7 @@ describe('redactAttributes', () => {
],
allocationInPercentage: 0.009076749759365777,
assetClass: 'FIXED_INCOME',
+ assetProfile: { holdings: [] },
assetSubClass: 'BOND',
countries: [],
dataSource: 'MANUAL',
@@ -1854,7 +1862,6 @@ describe('redactAttributes', () => {
grossPerformancePercent: 0,
grossPerformancePercentWithCurrencyEffect: -0.06153834320225245,
grossPerformanceWithCurrencyEffect: null,
- holdings: [],
investment: null,
name: 'Bondora Go & Grow',
netPerformance: null,
@@ -1896,6 +1903,7 @@ describe('redactAttributes', () => {
],
allocationInPercentage: 0.09095764645669335,
assetClass: 'EQUITY',
+ assetProfile: { holdings: [] },
assetSubClass: 'ETF',
countries: [
{
@@ -1958,7 +1966,6 @@ describe('redactAttributes', () => {
grossPerformancePercent: 0.27579517683678895,
grossPerformancePercentWithCurrencyEffect: 0.458553421589667,
grossPerformanceWithCurrencyEffect: null,
- holdings: [],
investment: null,
name: 'frankly Extreme 95 Index',
netPerformance: null,
@@ -1994,6 +2001,7 @@ describe('redactAttributes', () => {
tags: [],
allocationInPercentage: 0.05222646409742627,
assetClass: 'EQUITY',
+ assetProfile: { holdings: [] },
assetSubClass: 'STOCK',
countries: [
{
@@ -2010,7 +2018,6 @@ describe('redactAttributes', () => {
grossPerformancePercent: 0.7865431171216295,
grossPerformancePercentWithCurrencyEffect: 0.7865431171216295,
grossPerformanceWithCurrencyEffect: null,
- holdings: [],
investment: null,
name: 'Microsoft Corporation',
netPerformance: null,
@@ -2046,6 +2053,7 @@ describe('redactAttributes', () => {
tags: [],
allocationInPercentage: 0.1589050142378352,
assetClass: 'EQUITY',
+ assetProfile: { holdings: [] },
assetSubClass: 'STOCK',
countries: [
{
@@ -2062,7 +2070,6 @@ describe('redactAttributes', () => {
grossPerformancePercent: 17.184314638161936,
grossPerformancePercentWithCurrencyEffect: 17.184314638161936,
grossPerformanceWithCurrencyEffect: null,
- holdings: [],
investment: null,
name: 'Tesla, Inc.',
netPerformance: null,
@@ -2098,6 +2105,85 @@ describe('redactAttributes', () => {
tags: [],
allocationInPercentage: 0.057358979326040366,
assetClass: 'EQUITY',
+ assetProfile: {
+ holdings: [
+ {
+ allocationInPercentage: 0.06099941636982121,
+ name: 'APPLE INC',
+ valueInBaseCurrency: null
+ },
+ {
+ allocationInPercentage: 0.05862464529372787,
+ name: 'MICROSOFT CORP',
+ valueInBaseCurrency: null
+ },
+ {
+ allocationInPercentage: 0.05156070760128074,
+ name: 'NVIDIA CORP',
+ valueInBaseCurrency: null
+ },
+ {
+ allocationInPercentage: 0.03301535551128066,
+ name: 'AMAZON COM INC',
+ valueInBaseCurrency: null
+ },
+ {
+ allocationInPercentage: 0.01962204914568647,
+ name: 'FACEBOOK CLASS A INC',
+ valueInBaseCurrency: null
+ },
+ {
+ allocationInPercentage: 0.01902835637666313,
+ name: 'ALPHABET INC CLASS A',
+ valueInBaseCurrency: null
+ },
+ {
+ allocationInPercentage: 0.01555676306627245,
+ name: 'ALPHABET INC CLASS C',
+ valueInBaseCurrency: null
+ },
+ {
+ allocationInPercentage: 0.01463100485016827,
+ name: 'BERKSHIRE HATHAWAY INC CLASS B',
+ valueInBaseCurrency: null
+ },
+ {
+ allocationInPercentage: 0.01403731208114493,
+ name: 'BROADCOM INC',
+ valueInBaseCurrency: null
+ },
+ {
+ allocationInPercentage: 0.01297067761476403,
+ name: 'ELI LILLY',
+ valueInBaseCurrency: null
+ },
+ {
+ allocationInPercentage: 0.0118637927911612,
+ name: 'TESLA INC',
+ valueInBaseCurrency: null
+ },
+ {
+ allocationInPercentage: 0.01152166475477487,
+ name: 'JPMORGAN CHASE & CO',
+ valueInBaseCurrency: null
+ },
+ {
+ allocationInPercentage: 0.0100324015375638,
+ name: 'EXXON MOBIL CORP',
+ valueInBaseCurrency: null
+ },
+ {
+ allocationInPercentage: 0.01000221376964736,
+ name: 'UNITEDHEALTH GROUP INC',
+ valueInBaseCurrency: null
+ },
+ {
+ allocationInPercentage: 0.007818631890358146,
+ name: 'VISA INC CLASS A',
+ valueInBaseCurrency: null
+ }
+ ]
+ },
assetSubClass: 'ETF',
countries: [
{
@@ -2144,83 +2230,6 @@ describe('redactAttributes', () => {
grossPerformancePercent: 0.8832083851170418,
grossPerformancePercentWithCurrencyEffect: 0.8832083851170418,
grossPerformanceWithCurrencyEffect: null,
- holdings: [
- {
- allocationInPercentage: 0.06099941636982121,
- name: 'APPLE INC',
- valueInBaseCurrency: null
- },
- {
- allocationInPercentage: 0.05862464529372787,
- name: 'MICROSOFT CORP',
- valueInBaseCurrency: null
- },
- {
- allocationInPercentage: 0.05156070760128074,
- name: 'NVIDIA CORP',
- valueInBaseCurrency: null
- },
- {
- allocationInPercentage: 0.03301535551128066,
- name: 'AMAZON COM INC',
- valueInBaseCurrency: null
- },
- {
- allocationInPercentage: 0.01962204914568647,
- name: 'FACEBOOK CLASS A INC',
- valueInBaseCurrency: null
- },
- {
- allocationInPercentage: 0.01902835637666313,
- name: 'ALPHABET INC CLASS A',
- valueInBaseCurrency: null
- },
- {
- allocationInPercentage: 0.01555676306627245,
- name: 'ALPHABET INC CLASS C',
- valueInBaseCurrency: null
- },
- {
- allocationInPercentage: 0.01463100485016827,
- name: 'BERKSHIRE HATHAWAY INC CLASS B',
- valueInBaseCurrency: null
- },
- {
- allocationInPercentage: 0.01403731208114493,
- name: 'BROADCOM INC',
- valueInBaseCurrency: null
- },
- {
- allocationInPercentage: 0.01297067761476403,
- name: 'ELI LILLY',
- valueInBaseCurrency: null
- },
- {
- allocationInPercentage: 0.0118637927911612,
- name: 'TESLA INC',
- valueInBaseCurrency: null
- },
- {
- allocationInPercentage: 0.01152166475477487,
- name: 'JPMORGAN CHASE & CO',
- valueInBaseCurrency: null
- },
- {
- allocationInPercentage: 0.0100324015375638,
- name: 'EXXON MOBIL CORP',
- valueInBaseCurrency: null
- },
- {
- allocationInPercentage: 0.01000221376964736,
- name: 'UNITEDHEALTH GROUP INC',
- valueInBaseCurrency: null
- },
- {
- allocationInPercentage: 0.007818631890358146,
- name: 'VISA INC CLASS A',
- valueInBaseCurrency: null
- }
- ],
investment: null,
name: 'Vanguard Total Stock Market Index Fund ETF Shares',
netPerformance: null,
@@ -2270,6 +2279,85 @@ describe('redactAttributes', () => {
tags: [],
allocationInPercentage: 0.09386983901959013,
assetClass: 'EQUITY',
+ assetProfile: {
+ holdings: [
+ {
+ allocationInPercentage: 0.042520261085,
+ name: 'APPLE INC',
+ valueInBaseCurrency: null
+ },
+ {
+ allocationInPercentage: 0.037017038038,
+ name: 'MICROSOFT CORP',
+ valueInBaseCurrency: null
+ },
+ {
+ allocationInPercentage: 0.018861883836,
+ name: 'AMAZON COM INC',
+ valueInBaseCurrency: null
+ },
+ {
+ allocationInPercentage: 0.017806548325,
+ name: 'NVIDIA CORP',
+ valueInBaseCurrency: null
+ },
+ {
+ allocationInPercentage: 0.012188534864,
+ name: 'ALPHABET INC CLASS A',
+ valueInBaseCurrency: null
+ },
+ {
+ allocationInPercentage: 0.010831709166,
+ name: 'ALPHABET INC CLASS C',
+ valueInBaseCurrency: null
+ },
+ {
+ allocationInPercentage: 0.010813551981,
+ name: 'TESLA INC',
+ valueInBaseCurrency: null
+ },
+ {
+ allocationInPercentage: 0.009934819182,
+ name: 'FACEBOOK CLASS A INC',
+ valueInBaseCurrency: null
+ },
+ {
+ allocationInPercentage: 0.007403227621000001,
+ name: 'BERKSHIRE HATHAWAY INC CLASS B',
+ valueInBaseCurrency: null
+ },
+ {
+ allocationInPercentage: 0.007076883908,
+ name: 'ELI LILLY',
+ valueInBaseCurrency: null
+ },
+ {
+ allocationInPercentage: 0.006844271583,
+ name: 'EXXON MOBIL CORP',
+ valueInBaseCurrency: null
+ },
+ {
+ allocationInPercentage: 0.006718061670999999,
+ name: 'UNITEDHEALTH GROUP INC',
+ valueInBaseCurrency: null
+ },
+ {
+ allocationInPercentage: 0.006456949621,
+ name: 'JPMORGAN CHASE & CO',
+ valueInBaseCurrency: null
+ },
+ {
+ allocationInPercentage: 0.006293890054,
+ name: 'TAIWAN SEMICONDUCTOR MANUFACTURING',
+ valueInBaseCurrency: null
+ },
+ {
+ allocationInPercentage: 0.00600392555,
+ name: 'VISA INC CLASS A',
+ valueInBaseCurrency: null
+ }
+ ]
+ },
assetSubClass: 'ETF',
countries: [
{
@@ -2539,83 +2627,6 @@ describe('redactAttributes', () => {
grossPerformancePercent: 0.3683200415015591,
grossPerformancePercentWithCurrencyEffect: 0.5806366182968891,
grossPerformanceWithCurrencyEffect: null,
- holdings: [
- {
- allocationInPercentage: 0.042520261085,
- name: 'APPLE INC',
- valueInBaseCurrency: null
- },
- {
- allocationInPercentage: 0.037017038038,
- name: 'MICROSOFT CORP',
- valueInBaseCurrency: null
- },
- {
- allocationInPercentage: 0.018861883836,
- name: 'AMAZON COM INC',
- valueInBaseCurrency: null
- },
- {
- allocationInPercentage: 0.017806548325,
- name: 'NVIDIA CORP',
- valueInBaseCurrency: null
- },
- {
- allocationInPercentage: 0.012188534864,
- name: 'ALPHABET INC CLASS A',
- valueInBaseCurrency: null
- },
- {
- allocationInPercentage: 0.010831709166,
- name: 'ALPHABET INC CLASS C',
- valueInBaseCurrency: null
- },
- {
- allocationInPercentage: 0.010813551981,
- name: 'TESLA INC',
- valueInBaseCurrency: null
- },
- {
- allocationInPercentage: 0.009934819182,
- name: 'FACEBOOK CLASS A INC',
- valueInBaseCurrency: null
- },
- {
- allocationInPercentage: 0.007403227621000001,
- name: 'BERKSHIRE HATHAWAY INC CLASS B',
- valueInBaseCurrency: null
- },
- {
- allocationInPercentage: 0.007076883908,
- name: 'ELI LILLY',
- valueInBaseCurrency: null
- },
- {
- allocationInPercentage: 0.006844271583,
- name: 'EXXON MOBIL CORP',
- valueInBaseCurrency: null
- },
- {
- allocationInPercentage: 0.006718061670999999,
- name: 'UNITEDHEALTH GROUP INC',
- valueInBaseCurrency: null
- },
- {
- allocationInPercentage: 0.006456949621,
- name: 'JPMORGAN CHASE & CO',
- valueInBaseCurrency: null
- },
- {
- allocationInPercentage: 0.006293890054,
- name: 'TAIWAN SEMICONDUCTOR MANUFACTURING',
- valueInBaseCurrency: null
- },
- {
- allocationInPercentage: 0.00600392555,
- name: 'VISA INC CLASS A',
- valueInBaseCurrency: null
- }
- ],
investment: null,
name: 'Vanguard FTSE All-World UCITS ETF',
netPerformance: null,
@@ -2670,6 +2681,80 @@ describe('redactAttributes', () => {
tags: [],
allocationInPercentage: 0.03598477442100562,
assetClass: 'EQUITY',
+ assetProfile: {
+ holdings: [
+ {
+ allocationInPercentage: 0.051778373,
+ name: 'APPLE INC',
+ valueInBaseCurrency: null
+ },
+ {
+ allocationInPercentage: 0.0403267055,
+ name: 'MICROSOFT CORP',
+ valueInBaseCurrency: null
+ },
+ {
+ allocationInPercentage: 0.0221895862,
+ name: 'AMAZON COM INC',
+ valueInBaseCurrency: null
+ },
+ {
+ allocationInPercentage: 0.0208100035,
+ name: 'NVIDIA CORP',
+ valueInBaseCurrency: null
+ },
+ {
+ allocationInPercentage: 0.0139820061,
+ name: 'ALPHABET INC CLASS A',
+ valueInBaseCurrency: null
+ },
+ {
+ allocationInPercentage: 0.0126263246,
+ name: 'ALPHABET INC CLASS C',
+ valueInBaseCurrency: null
+ },
+ {
+ allocationInPercentage: 0.0121596126,
+ name: 'TESLA INC',
+ valueInBaseCurrency: null
+ },
+ {
+ allocationInPercentage: 0.0114079282,
+ name: 'FACEBOOK CLASS A INC',
+ valueInBaseCurrency: null
+ },
+ {
+ allocationInPercentage: 0.0081570352,
+ name: 'BERKSHIRE HATHAWAY INC CLASS B',
+ valueInBaseCurrency: null
+ },
+ {
+ allocationInPercentage: 0.0079471416,
+ name: 'EXXON MOBIL CORP',
+ valueInBaseCurrency: null
+ },
+ {
+ allocationInPercentage: 0.0078190388,
+ name: 'ELI LILLY',
+ valueInBaseCurrency: null
+ },
+ {
+ allocationInPercentage: 0.0077121293,
+ name: 'UNITEDHEALTH GROUP INC',
+ valueInBaseCurrency: null
+ },
+ {
+ allocationInPercentage: 0.0074484861,
+ name: 'JPMORGAN CHASE & CO',
+ valueInBaseCurrency: null
+ },
+ {
+ allocationInPercentage: 0.006978079,
+ name: 'VISA INC CLASS A',
+ valueInBaseCurrency: null
+ }
+ ]
+ },
assetSubClass: 'ETF',
countries: [
{
@@ -2818,78 +2903,6 @@ describe('redactAttributes', () => {
grossPerformancePercent: 0.3474381850624522,
grossPerformancePercentWithCurrencyEffect: 0.28744846894552306,
grossPerformanceWithCurrencyEffect: null,
- holdings: [
- {
- allocationInPercentage: 0.051778373,
- name: 'APPLE INC',
- valueInBaseCurrency: null
- },
- {
- allocationInPercentage: 0.0403267055,
- name: 'MICROSOFT CORP',
- valueInBaseCurrency: null
- },
- {
- allocationInPercentage: 0.0221895862,
- name: 'AMAZON COM INC',
- valueInBaseCurrency: null
- },
- {
- allocationInPercentage: 0.0208100035,
- name: 'NVIDIA CORP',
- valueInBaseCurrency: null
- },
- {
- allocationInPercentage: 0.0139820061,
- name: 'ALPHABET INC CLASS A',
- valueInBaseCurrency: null
- },
- {
- allocationInPercentage: 0.0126263246,
- name: 'ALPHABET INC CLASS C',
- valueInBaseCurrency: null
- },
- {
- allocationInPercentage: 0.0121596126,
- name: 'TESLA INC',
- valueInBaseCurrency: null
- },
- {
- allocationInPercentage: 0.0114079282,
- name: 'FACEBOOK CLASS A INC',
- valueInBaseCurrency: null
- },
- {
- allocationInPercentage: 0.0081570352,
- name: 'BERKSHIRE HATHAWAY INC CLASS B',
- valueInBaseCurrency: null
- },
- {
- allocationInPercentage: 0.0079471416,
- name: 'EXXON MOBIL CORP',
- valueInBaseCurrency: null
- },
- {
- allocationInPercentage: 0.0078190388,
- name: 'ELI LILLY',
- valueInBaseCurrency: null
- },
- {
- allocationInPercentage: 0.0077121293,
- name: 'UNITEDHEALTH GROUP INC',
- valueInBaseCurrency: null
- },
- {
- allocationInPercentage: 0.0074484861,
- name: 'JPMORGAN CHASE & CO',
- valueInBaseCurrency: null
- },
- {
- allocationInPercentage: 0.006978079,
- name: 'VISA INC CLASS A',
- valueInBaseCurrency: null
- }
- ],
investment: null,
name: 'Xtrackers MSCI World UCITS ETF 1C',
netPerformance: null,
@@ -2930,6 +2943,7 @@ describe('redactAttributes', () => {
currency: 'USD',
allocationInPercentage: 0.20291717628620132,
assetClass: 'LIQUIDITY',
+ assetProfile: { holdings: [] },
assetSubClass: 'CASH',
countries: [],
dividend: null,
@@ -2937,7 +2951,6 @@ describe('redactAttributes', () => {
grossPerformancePercent: 0,
grossPerformancePercentWithCurrencyEffect: 0,
grossPerformanceWithCurrencyEffect: null,
- holdings: [],
investment: null,
marketPrice: 0,
name: 'USD',
@@ -3237,4 +3250,101 @@ describe('redactAttributes', () => {
]
});
});
+
+ // The type makes sure that the object keeps the shape of the response, in
+ // which the holdings of an ETF are part of its asset profile
+ it('should redact the values of the holdings of an ETF', () => {
+ expect(
+ redactPaths({
+ object: {
+ holdings: [
+ {
+ activitiesCount: 5,
+ allocationInPercentage: 0.057358979326040366,
+ assetProfile: {
+ assetClass: 'EQUITY',
+ assetSubClass: 'ETF',
+ countries: [],
+ currency: 'USD',
+ dataSource: 'YAHOO',
+ holdings: [
+ {
+ allocationInPercentage: 0.06099941636982121,
+ name: 'APPLE INC',
+ valueInBaseCurrency: 860.2442693554036
+ },
+ {
+ allocationInPercentage: 0.05862464529372787,
+ name: 'MICROSOFT CORP',
+ valueInBaseCurrency: 826.7540602547973
+ }
+ ],
+ name: 'Vanguard Total Stock Market Index Fund ETF Shares',
+ sectors: [],
+ symbol: 'VTI'
+ },
+ dateOfFirstActivity: new Date('2019-02-28T23:00:00.000Z'),
+ dividend: 0,
+ grossPerformance: 5856.3,
+ grossPerformancePercent: 0.8832083851170418,
+ grossPerformancePercentWithCurrencyEffect: 0.8832083851170418,
+ grossPerformanceWithCurrencyEffect: 5856.3,
+ investment: 8246.2,
+ marketPrice: 282.05,
+ netPerformance: 5756.8,
+ netPerformancePercent: 0.8682024540139314,
+ netPerformancePercentWithCurrencyEffect: 0.8328704068843998,
+ netPerformanceWithCurrencyEffect: 5756.8,
+ quantity: 50,
+ valueInBaseCurrency: 14102.5
+ }
+ ]
+ } satisfies Pick,
+ paths: DEFAULT_REDACTED_PATHS
+ })
+ ).toStrictEqual({
+ holdings: [
+ {
+ activitiesCount: 5,
+ allocationInPercentage: 0.057358979326040366,
+ assetProfile: {
+ assetClass: 'EQUITY',
+ assetSubClass: 'ETF',
+ countries: [],
+ currency: 'USD',
+ dataSource: 'YAHOO',
+ holdings: [
+ {
+ allocationInPercentage: 0.06099941636982121,
+ name: 'APPLE INC',
+ valueInBaseCurrency: null
+ },
+ {
+ allocationInPercentage: 0.05862464529372787,
+ name: 'MICROSOFT CORP',
+ valueInBaseCurrency: null
+ }
+ ],
+ name: 'Vanguard Total Stock Market Index Fund ETF Shares',
+ sectors: [],
+ symbol: 'VTI'
+ },
+ dateOfFirstActivity: '2019-02-28T23:00:00.000Z',
+ dividend: null,
+ grossPerformance: null,
+ grossPerformancePercent: 0.8832083851170418,
+ grossPerformancePercentWithCurrencyEffect: 0.8832083851170418,
+ grossPerformanceWithCurrencyEffect: null,
+ investment: null,
+ marketPrice: 282.05,
+ netPerformance: null,
+ netPerformancePercent: 0.8682024540139314,
+ netPerformancePercentWithCurrencyEffect: 0.8328704068843998,
+ netPerformanceWithCurrencyEffect: null,
+ quantity: null,
+ valueInBaseCurrency: null
+ }
+ ]
+ });
+ });
});
diff --git a/libs/common/src/lib/config.ts b/libs/common/src/lib/config.ts
index beedb741cb..03d36f577b 100644
--- a/libs/common/src/lib/config.ts
+++ b/libs/common/src/lib/config.ts
@@ -158,10 +158,10 @@ export const DEFAULT_REDACTED_PATHS = [
'grossPerformance',
'grossPerformanceWithCurrencyEffect',
'historicalData[*].quantity',
+ 'holdings[*].assetProfile.holdings[*].valueInBaseCurrency',
'holdings[*].dividend',
'holdings[*].grossPerformance',
'holdings[*].grossPerformanceWithCurrencyEffect',
- 'holdings[*].holdings[*].valueInBaseCurrency',
'holdings[*].investment',
'holdings[*].netPerformance',
'holdings[*].netPerformanceWithCurrencyEffect',
From f46f77f352d50728361d62ed73613d69acf67e80 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Szymon=20=C5=81=C4=85giewka?=
Date: Wed, 30 Sep 2026 10:15:01 +0200
Subject: [PATCH 13/27] Task/migrate from lodash to lodash-es (#7850)
* Migrate from lodash to lodash-es
* Update changelog
---
CHANGELOG.md | 1 +
.../account-balance.service.ts | 2 +-
apps/api/src/app/account/account.service.ts | 2 +-
.../app/activities/activities-filter.dto.ts | 2 +-
.../src/app/activities/activities.service.ts | 2 +-
apps/api/src/app/asset/asset.controller.ts | 2 +-
.../asset-profiles/asset-profiles.service.ts | 2 +-
.../benchmarks/benchmarks.service.ts | 2 +-
.../ghostfolio/ghostfolio.service.ts | 2 +-
apps/api/src/app/export/export.service.ts | 2 +-
apps/api/src/app/export/get-export.dto.ts | 2 +-
apps/api/src/app/import/import.service.ts | 2 +-
apps/api/src/app/info/info.service.ts | 2 +-
.../calculator/portfolio-calculator.ts | 2 +-
.../calculator/roai/portfolio-calculator.ts | 2 +-
.../src/app/portfolio/current-rate.service.ts | 2 +-
.../src/app/portfolio/portfolio.service.ts | 2 +-
apps/api/src/app/symbol/symbol.controller.ts | 2 +-
apps/api/src/app/user/user.controller.ts | 2 +-
apps/api/src/app/user/user.service.ts | 2 +-
apps/api/src/dtos/filter.dto.ts | 2 +-
apps/api/src/helper/activity.helper.ts | 2 +-
apps/api/src/helper/object.helper.ts | 2 +-
apps/api/src/models/rule.ts | 2 +-
.../services/benchmark/benchmark.service.ts | 2 +-
.../data-provider/data-provider.service.ts | 2 +-
.../eod-historical-data.service.ts | 2 +-
.../financial-modeling-prep.service.ts | 2 +-
.../yahoo-finance/yahoo-finance.service.ts | 2 +-
.../exchange-rate-data.service.ts | 2 +-
.../data-gathering/data-gathering.service.ts | 2 +-
.../twitter-bot/twitter-bot.service.ts | 2 +-
apps/client/jest.config.ts | 2 +-
.../account-detail-dialog.component.ts | 2 +-
.../admin-jobs/admin-jobs.component.ts | 2 +-
.../asset-profile-dialog.component.ts | 2 +-
.../admin-settings.component.ts | 2 +-
.../holding-detail-dialog.component.ts | 2 +-
.../portfolio-performance.component.ts | 2 +-
.../portfolio-summary.component.ts | 2 +-
.../src/app/pages/api/api-page.component.ts | 2 +-
.../import-activities-dialog.component.ts | 2 +-
.../allocations/allocations-page.component.ts | 2 +-
.../analysis/analysis-page.component.ts | 2 +-
.../portfolio/fire/fire-page.component.ts | 2 +-
.../app/services/import-activities.service.ts | 2 +-
eslint.config.cjs | 19 ++++++++++++++++++-
jest.preset.js | 5 ++++-
libs/common/src/lib/calculation-helper.ts | 2 +-
.../common/src/lib/dtos/create-account.dto.ts | 2 +-
libs/common/src/lib/dtos/create-order.dto.ts | 2 +-
.../common/src/lib/dtos/update-account.dto.ts | 2 +-
libs/common/src/lib/dtos/update-order.dto.ts | 2 +-
libs/common/src/lib/helper.ts | 2 +-
libs/ui/jest.config.ts | 2 +-
.../account-balances.component.ts | 2 +-
.../activities-filter.component.ts | 2 +-
.../src/lib/assistant/assistant.component.ts | 2 +-
.../src/lib/benchmark/benchmark.component.ts | 2 +-
.../fire-calculator.component.ts | 2 +-
...cal-market-data-editor-dialog.component.ts | 2 +-
.../lib/notifications/notification.service.ts | 2 +-
libs/ui/src/lib/services/admin.service.ts | 2 +-
libs/ui/src/lib/services/data.service.ts | 2 +-
.../symbol-autocomplete.component.ts | 2 +-
.../top-holdings/top-holdings.component.ts | 2 +-
.../treemap-chart/treemap-chart.component.ts | 2 +-
libs/ui/src/lib/value/value.component.ts | 2 +-
package-lock.json | 15 ++++++++++++---
package.json | 4 ++--
70 files changed, 102 insertions(+), 72 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4bbf0a08dd..c9937fcb31 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
- Extended the emergency fund dialog in the portfolio summary to include the currency suffix
- Migrated the client to the code generated by the new `prisma-client` generator of _Prisma_
+- Migrated from `lodash` to `lodash-es`
### Fixed
diff --git a/apps/api/src/app/account-balance/account-balance.service.ts b/apps/api/src/app/account-balance/account-balance.service.ts
index 3823498df3..ad990c66bd 100644
--- a/apps/api/src/app/account-balance/account-balance.service.ts
+++ b/apps/api/src/app/account-balance/account-balance.service.ts
@@ -24,7 +24,7 @@ import { EventEmitter2 } from '@nestjs/event-emitter';
import { AccountBalance, Prisma } from '@prisma/client';
import { Big } from 'big.js';
import { endOfToday, format, min, parseISO } from 'date-fns';
-import { groupBy } from 'lodash';
+import { groupBy } from 'lodash-es';
@Injectable()
export class AccountBalanceService {
diff --git a/apps/api/src/app/account/account.service.ts b/apps/api/src/app/account/account.service.ts
index 59315602a8..d1a895a5a3 100644
--- a/apps/api/src/app/account/account.service.ts
+++ b/apps/api/src/app/account/account.service.ts
@@ -28,7 +28,7 @@ import {
} from '@prisma/client';
import { Big } from 'big.js';
import { format } from 'date-fns';
-import { groupBy, isNil } from 'lodash';
+import { groupBy, isNil } from 'lodash-es';
import { CashDetails } from './interfaces/cash-details.interface';
diff --git a/apps/api/src/app/activities/activities-filter.dto.ts b/apps/api/src/app/activities/activities-filter.dto.ts
index 0c56aacc85..17385148cc 100644
--- a/apps/api/src/app/activities/activities-filter.dto.ts
+++ b/apps/api/src/app/activities/activities-filter.dto.ts
@@ -5,7 +5,7 @@ import { DateRange } from '@ghostfolio/common/types';
import { Type as ActivityType } from '@prisma/client';
import { Transform, TransformFnParams } from 'class-transformer';
import { IsEnum, IsOptional, Matches } from 'class-validator';
-import { isString } from 'lodash';
+import { isString } from 'lodash-es';
export class ActivitiesFilterDto extends FilterDto {
@IsEnum(ActivityType, { each: true })
diff --git a/apps/api/src/app/activities/activities.service.ts b/apps/api/src/app/activities/activities.service.ts
index ecc2296a90..59d1ba9c33 100644
--- a/apps/api/src/app/activities/activities.service.ts
+++ b/apps/api/src/app/activities/activities.service.ts
@@ -60,7 +60,7 @@ import {
Type as ActivityType
} from '@prisma/client';
import { Big } from 'big.js';
-import { groupBy, uniqBy } from 'lodash';
+import { groupBy, uniqBy } from 'lodash-es';
import { randomUUID } from 'node:crypto';
@Injectable()
diff --git a/apps/api/src/app/asset/asset.controller.ts b/apps/api/src/app/asset/asset.controller.ts
index 49afb43d71..352d3e18b1 100644
--- a/apps/api/src/app/asset/asset.controller.ts
+++ b/apps/api/src/app/asset/asset.controller.ts
@@ -5,7 +5,7 @@ import type { AssetResponse } from '@ghostfolio/common/interfaces';
import { Controller, Get, Param, UseInterceptors } from '@nestjs/common';
import { DataSource } from '@prisma/client';
-import { pick } from 'lodash';
+import { pick } from 'lodash-es';
@Controller('asset')
export class AssetController {
diff --git a/apps/api/src/app/endpoints/asset-profiles/asset-profiles.service.ts b/apps/api/src/app/endpoints/asset-profiles/asset-profiles.service.ts
index e9aad6911f..4a2507e697 100644
--- a/apps/api/src/app/endpoints/asset-profiles/asset-profiles.service.ts
+++ b/apps/api/src/app/endpoints/asset-profiles/asset-profiles.service.ts
@@ -29,7 +29,7 @@ import { MarketDataPreset } from '@ghostfolio/common/types';
import { Injectable, Logger, NotFoundException } from '@nestjs/common';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { AssetClass, AssetSubClass, DataSource, Prisma } from '@prisma/client';
-import { groupBy } from 'lodash';
+import { groupBy } from 'lodash-es';
@Injectable()
export class AssetProfilesService {
diff --git a/apps/api/src/app/endpoints/benchmarks/benchmarks.service.ts b/apps/api/src/app/endpoints/benchmarks/benchmarks.service.ts
index beff7ffa60..908db59b7f 100644
--- a/apps/api/src/app/endpoints/benchmarks/benchmarks.service.ts
+++ b/apps/api/src/app/endpoints/benchmarks/benchmarks.service.ts
@@ -14,7 +14,7 @@ import { DateRange } from '@ghostfolio/common/types';
import { Injectable, Logger } from '@nestjs/common';
import { format, isSameDay } from 'date-fns';
-import { isNumber } from 'lodash';
+import { isNumber } from 'lodash-es';
@Injectable()
export class BenchmarksService {
diff --git a/apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.service.ts b/apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.service.ts
index ec0eeeb868..4042241449 100644
--- a/apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.service.ts
+++ b/apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.service.ts
@@ -40,7 +40,7 @@ import { Injectable, Logger } from '@nestjs/common';
import { DataSource, SymbolProfile } from '@prisma/client';
import { Big } from 'big.js';
import { addMilliseconds, isBefore } from 'date-fns';
-import { isEmpty } from 'lodash';
+import { isEmpty } from 'lodash-es';
@Injectable()
export class GhostfolioService {
diff --git a/apps/api/src/app/export/export.service.ts b/apps/api/src/app/export/export.service.ts
index 35db209935..db08f62052 100644
--- a/apps/api/src/app/export/export.service.ts
+++ b/apps/api/src/app/export/export.service.ts
@@ -11,7 +11,7 @@ import {
import { Injectable } from '@nestjs/common';
import { Platform, Prisma, Type as ActivityType } from '@prisma/client';
-import { groupBy, uniqBy } from 'lodash';
+import { groupBy, uniqBy } from 'lodash-es';
@Injectable()
export class ExportService {
diff --git a/apps/api/src/app/export/get-export.dto.ts b/apps/api/src/app/export/get-export.dto.ts
index 5fc3c81baa..524c357112 100644
--- a/apps/api/src/app/export/get-export.dto.ts
+++ b/apps/api/src/app/export/get-export.dto.ts
@@ -2,7 +2,7 @@ import { ActivitiesFilterDto } from '@ghostfolio/api/app/activities/activities-f
import { Transform, TransformFnParams } from 'class-transformer';
import { IsOptional, IsUUID } from 'class-validator';
-import { isString } from 'lodash';
+import { isString } from 'lodash-es';
export class GetExportDto extends ActivitiesFilterDto {
@IsOptional()
diff --git a/apps/api/src/app/import/import.service.ts b/apps/api/src/app/import/import.service.ts
index 5f202f4ac4..5afa4c33dd 100644
--- a/apps/api/src/app/import/import.service.ts
+++ b/apps/api/src/app/import/import.service.ts
@@ -47,7 +47,7 @@ import { Account, DataSource, Prisma } from '@prisma/client';
import { Big } from 'big.js';
import { isISIN } from 'class-validator';
import { isSameSecond, parseISO } from 'date-fns';
-import { omit, uniqBy } from 'lodash';
+import { omit, uniqBy } from 'lodash-es';
import { randomUUID } from 'node:crypto';
import { ImportValidationError } from './errors/import-validation.error';
diff --git a/apps/api/src/app/info/info.service.ts b/apps/api/src/app/info/info.service.ts
index a0759b1698..2a7382446c 100644
--- a/apps/api/src/app/info/info.service.ts
+++ b/apps/api/src/app/info/info.service.ts
@@ -26,7 +26,7 @@ import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { MarketData } from '@prisma/client';
import { subDays } from 'date-fns';
-import { isNil } from 'lodash';
+import { isNil } from 'lodash-es';
@Injectable()
export class InfoService {
diff --git a/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts b/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts
index d868b65b3f..e03b530258 100644
--- a/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts
+++ b/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts
@@ -72,7 +72,7 @@ import {
startOfYear,
subDays
} from 'date-fns';
-import { groupBy, sortBy, uniqBy } from 'lodash';
+import { groupBy, sortBy, uniqBy } from 'lodash-es';
export abstract class PortfolioCalculator {
protected static readonly ENABLE_LOGGING = false;
diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator.ts
index 2b7b4e6dce..c0b7337350 100644
--- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator.ts
+++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator.ts
@@ -32,7 +32,7 @@ import {
isBefore,
isThisYear
} from 'date-fns';
-import { sum } from 'lodash';
+import { sum } from 'lodash-es';
export class RoaiPortfolioCalculator extends PortfolioCalculator {
protected calculateOverallPerformance(
diff --git a/apps/api/src/app/portfolio/current-rate.service.ts b/apps/api/src/app/portfolio/current-rate.service.ts
index 79363c6344..80d37259a4 100644
--- a/apps/api/src/app/portfolio/current-rate.service.ts
+++ b/apps/api/src/app/portfolio/current-rate.service.ts
@@ -14,7 +14,7 @@ import {
import { Injectable } from '@nestjs/common';
import { isBefore, isToday } from 'date-fns';
-import { isEmpty, uniqBy } from 'lodash';
+import { isEmpty, uniqBy } from 'lodash-es';
import { GetValueObject } from './interfaces/get-value-object.interface';
import { GetValuesObject } from './interfaces/get-values-object.interface';
diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts
index 7350f5fc01..72c5b5946a 100644
--- a/apps/api/src/app/portfolio/portfolio.service.ts
+++ b/apps/api/src/app/portfolio/portfolio.service.ts
@@ -104,7 +104,7 @@ import {
set,
startOfDay
} from 'date-fns';
-import { groupBy } from 'lodash';
+import { groupBy } from 'lodash-es';
import { PortfolioCalculator } from './calculator/portfolio-calculator';
import { PortfolioCalculatorFactory } from './calculator/portfolio-calculator.factory';
diff --git a/apps/api/src/app/symbol/symbol.controller.ts b/apps/api/src/app/symbol/symbol.controller.ts
index 9d8abb47ba..cf26d39d54 100644
--- a/apps/api/src/app/symbol/symbol.controller.ts
+++ b/apps/api/src/app/symbol/symbol.controller.ts
@@ -25,7 +25,7 @@ import { AuthGuard } from '@nestjs/passport';
import { DataSource } from '@prisma/client';
import { isValid, parseISO } from 'date-fns';
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
-import { isEmpty } from 'lodash';
+import { isEmpty } from 'lodash-es';
import { GetLookupDto } from './get-lookup.dto';
import { SymbolService } from './symbol.service';
diff --git a/apps/api/src/app/user/user.controller.ts b/apps/api/src/app/user/user.controller.ts
index ae9e7d680b..32ac353351 100644
--- a/apps/api/src/app/user/user.controller.ts
+++ b/apps/api/src/app/user/user.controller.ts
@@ -53,7 +53,7 @@ import { AuthGuard } from '@nestjs/passport';
import { Throttle } from '@nestjs/throttler';
import { User as UserModel } from '@prisma/client';
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
-import { merge, size } from 'lodash';
+import { merge, size } from 'lodash-es';
import { UserService } from './user.service';
diff --git a/apps/api/src/app/user/user.service.ts b/apps/api/src/app/user/user.service.ts
index a74e32ca93..2e54a7ddc3 100644
--- a/apps/api/src/app/user/user.service.ts
+++ b/apps/api/src/app/user/user.service.ts
@@ -46,7 +46,7 @@ import { EventEmitter2 } from '@nestjs/event-emitter';
import { InjectThrottlerStorage, ThrottlerStorage } from '@nestjs/throttler';
import { Prisma, Role, User } from '@prisma/client';
import { differenceInDays, subDays } from 'date-fns';
-import { isNil, without } from 'lodash';
+import { isNil, without } from 'lodash-es';
import { createHmac } from 'node:crypto';
@Injectable()
diff --git a/apps/api/src/dtos/filter.dto.ts b/apps/api/src/dtos/filter.dto.ts
index 09206984ab..84d9ffc0d0 100644
--- a/apps/api/src/dtos/filter.dto.ts
+++ b/apps/api/src/dtos/filter.dto.ts
@@ -9,7 +9,7 @@ import {
IsUUID,
MaxLength
} from 'class-validator';
-import { isString } from 'lodash';
+import { isString } from 'lodash-es';
export class FilterDto {
@IsOptional()
diff --git a/apps/api/src/helper/activity.helper.ts b/apps/api/src/helper/activity.helper.ts
index b281106c77..f7cb20505e 100644
--- a/apps/api/src/helper/activity.helper.ts
+++ b/apps/api/src/helper/activity.helper.ts
@@ -5,7 +5,7 @@ import {
import { Prisma, Type as ActivityType } from '@prisma/client';
import { endOfToday, isAfter } from 'date-fns';
-import { uniqBy } from 'lodash';
+import { uniqBy } from 'lodash-es';
export const WHERE_ACTIVITY_NOT_DRAFT: Prisma.OrderWhereInput = {
tags: {
diff --git a/apps/api/src/helper/object.helper.ts b/apps/api/src/helper/object.helper.ts
index 350d5fe04a..c040c5e0fc 100644
--- a/apps/api/src/helper/object.helper.ts
+++ b/apps/api/src/helper/object.helper.ts
@@ -1,6 +1,6 @@
import fastRedact from 'fast-redact';
import jsonpath from 'jsonpath';
-import { cloneDeep, isObject } from 'lodash';
+import { cloneDeep, isObject } from 'lodash-es';
export function hasNotDefinedValuesInObject(aObject: Object): boolean {
for (const key in aObject) {
diff --git a/apps/api/src/models/rule.ts b/apps/api/src/models/rule.ts
index 4985b26477..f7f6c53637 100644
--- a/apps/api/src/models/rule.ts
+++ b/apps/api/src/models/rule.ts
@@ -7,7 +7,7 @@ import {
} from '@ghostfolio/common/interfaces';
import { Big } from 'big.js';
-import { groupBy } from 'lodash';
+import { groupBy } from 'lodash-es';
import { EvaluationResult } from './interfaces/evaluation-result.interface';
import { RuleInterface } from './interfaces/rule.interface';
diff --git a/apps/api/src/services/benchmark/benchmark.service.ts b/apps/api/src/services/benchmark/benchmark.service.ts
index 953939caac..4ea27b2f2e 100644
--- a/apps/api/src/services/benchmark/benchmark.service.ts
+++ b/apps/api/src/services/benchmark/benchmark.service.ts
@@ -23,7 +23,7 @@ import { Injectable, Logger } from '@nestjs/common';
import { SymbolProfile } from '@prisma/client';
import { Big } from 'big.js';
import { addHours, isPast, subDays } from 'date-fns';
-import { round, uniqBy } from 'lodash';
+import { round, uniqBy } from 'lodash-es';
import ms from 'ms';
import { BenchmarkValue } from './interfaces/benchmark-value.interface';
diff --git a/apps/api/src/services/data-provider/data-provider.service.ts b/apps/api/src/services/data-provider/data-provider.service.ts
index 87c1e85c61..cfacab3231 100644
--- a/apps/api/src/services/data-provider/data-provider.service.ts
+++ b/apps/api/src/services/data-provider/data-provider.service.ts
@@ -42,7 +42,7 @@ import { Inject, Injectable, Logger, OnModuleInit } from '@nestjs/common';
import { DataSource, MarketData, Prisma, SymbolProfile } from '@prisma/client';
import { Big } from 'big.js';
import { eachDayOfInterval, format, isValid } from 'date-fns';
-import { groupBy, isEmpty, isNumber, omit, uniqWith } from 'lodash';
+import { groupBy, isEmpty, isNumber, omit, uniqWith } from 'lodash-es';
import ms from 'ms';
import { AssetProfileInvalidError } from './errors/asset-profile-invalid.error';
diff --git a/apps/api/src/services/data-provider/eod-historical-data/eod-historical-data.service.ts b/apps/api/src/services/data-provider/eod-historical-data/eod-historical-data.service.ts
index 6bc003fc23..7dbb3b859c 100644
--- a/apps/api/src/services/data-provider/eod-historical-data/eod-historical-data.service.ts
+++ b/apps/api/src/services/data-provider/eod-historical-data/eod-historical-data.service.ts
@@ -31,7 +31,7 @@ import {
SymbolProfile
} from '@prisma/client';
import { addDays, format, isSameDay, isToday } from 'date-fns';
-import { isNumber } from 'lodash';
+import { isNumber } from 'lodash-es';
@Injectable()
export class EodHistoricalDataService
diff --git a/apps/api/src/services/data-provider/financial-modeling-prep/financial-modeling-prep.service.ts b/apps/api/src/services/data-provider/financial-modeling-prep/financial-modeling-prep.service.ts
index 7c840b5fc6..a8f1c6287c 100644
--- a/apps/api/src/services/data-provider/financial-modeling-prep/financial-modeling-prep.service.ts
+++ b/apps/api/src/services/data-provider/financial-modeling-prep/financial-modeling-prep.service.ts
@@ -47,7 +47,7 @@ import {
isSameDay,
parseISO
} from 'date-fns';
-import { isArray, uniqBy } from 'lodash';
+import { isArray, uniqBy } from 'lodash-es';
@Injectable()
export class FinancialModelingPrepService
diff --git a/apps/api/src/services/data-provider/yahoo-finance/yahoo-finance.service.ts b/apps/api/src/services/data-provider/yahoo-finance/yahoo-finance.service.ts
index ca6357e257..d2b741d372 100644
--- a/apps/api/src/services/data-provider/yahoo-finance/yahoo-finance.service.ts
+++ b/apps/api/src/services/data-provider/yahoo-finance/yahoo-finance.service.ts
@@ -24,7 +24,7 @@ import { Injectable, Logger } from '@nestjs/common';
import { DataSource, SymbolProfile } from '@prisma/client';
import { addDays, format, isSameDay } from 'date-fns';
import { ReasonPhrases, StatusCodes } from 'http-status-codes';
-import { uniqBy } from 'lodash';
+import { uniqBy } from 'lodash-es';
import YahooFinance from 'yahoo-finance2';
import type { ChartResultArray } from 'yahoo-finance2/modules/chart';
import type {
diff --git a/apps/api/src/services/exchange-rate-data/exchange-rate-data.service.ts b/apps/api/src/services/exchange-rate-data/exchange-rate-data.service.ts
index 0d7e98e2e0..824149e74f 100644
--- a/apps/api/src/services/exchange-rate-data/exchange-rate-data.service.ts
+++ b/apps/api/src/services/exchange-rate-data/exchange-rate-data.service.ts
@@ -26,7 +26,7 @@ import {
isToday,
subDays
} from 'date-fns';
-import { isNumber } from 'lodash';
+import { isNumber } from 'lodash-es';
import ms from 'ms';
import { ExchangeRatesByCurrency } from './interfaces/exchange-rate-data.interface';
diff --git a/apps/api/src/services/queues/data-gathering/data-gathering.service.ts b/apps/api/src/services/queues/data-gathering/data-gathering.service.ts
index 4cbe6969ae..3a98757591 100644
--- a/apps/api/src/services/queues/data-gathering/data-gathering.service.ts
+++ b/apps/api/src/services/queues/data-gathering/data-gathering.service.ts
@@ -33,7 +33,7 @@ import { Inject, Injectable, Logger } from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { Job, JobOptions, Queue } from 'bull';
import { format, min, subDays, subMilliseconds, subYears } from 'date-fns';
-import { isEmpty } from 'lodash';
+import { isEmpty } from 'lodash-es';
import ms, { StringValue } from 'ms';
@Injectable()
diff --git a/apps/api/src/services/twitter-bot/twitter-bot.service.ts b/apps/api/src/services/twitter-bot/twitter-bot.service.ts
index 2dbed82d21..6282b4363b 100644
--- a/apps/api/src/services/twitter-bot/twitter-bot.service.ts
+++ b/apps/api/src/services/twitter-bot/twitter-bot.service.ts
@@ -10,7 +10,7 @@ import {
import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
import { isWeekend } from 'date-fns';
-import { round } from 'lodash';
+import { round } from 'lodash-es';
import { TwitterApi, TwitterApiReadWrite } from 'twitter-api-v2';
@Injectable()
diff --git a/apps/client/jest.config.ts b/apps/client/jest.config.ts
index 26d772e11f..d60a24ae4f 100644
--- a/apps/client/jest.config.ts
+++ b/apps/client/jest.config.ts
@@ -18,6 +18,6 @@ export default {
}
]
},
- transformIgnorePatterns: ['node_modules/(?!.*.mjs$)'],
+ transformIgnorePatterns: ['node_modules/(?!.*.mjs$|lodash-es/)'],
preset: '../../jest.preset.js'
};
diff --git a/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts b/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts
index b8c7f16dbc..44f7280473 100644
--- a/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts
+++ b/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts
@@ -55,7 +55,7 @@ import {
readerOutline,
swapVerticalOutline
} from 'ionicons/icons';
-import { isNumber } from 'lodash';
+import { isNumber } from 'lodash-es';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
import { filter, forkJoin } from 'rxjs';
diff --git a/apps/client/src/app/components/admin-jobs/admin-jobs.component.ts b/apps/client/src/app/components/admin-jobs/admin-jobs.component.ts
index fd90bff2c1..e8e3a21255 100644
--- a/apps/client/src/app/components/admin-jobs/admin-jobs.component.ts
+++ b/apps/client/src/app/components/admin-jobs/admin-jobs.component.ts
@@ -49,7 +49,7 @@ import {
removeCircleOutline,
timeOutline
} from 'ionicons/icons';
-import { get } from 'lodash';
+import { get } from 'lodash-es';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
@Component({
diff --git a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts
index 154211b918..ba119b565f 100644
--- a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts
+++ b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts
@@ -106,7 +106,7 @@ import {
serverOutline,
trashOutline
} from 'ionicons/icons';
-import { isBoolean } from 'lodash';
+import { isBoolean } from 'lodash-es';
import ms from 'ms';
import { EMPTY } from 'rxjs';
import { catchError } from 'rxjs/operators';
diff --git a/apps/client/src/app/components/admin-settings/admin-settings.component.ts b/apps/client/src/app/components/admin-settings/admin-settings.component.ts
index b0c3dc2d22..d49f3fb0aa 100644
--- a/apps/client/src/app/components/admin-settings/admin-settings.component.ts
+++ b/apps/client/src/app/components/admin-settings/admin-settings.component.ts
@@ -42,7 +42,7 @@ import { RouterModule } from '@angular/router';
import { IonIcon } from '@ionic/angular/standalone';
import { addIcons } from 'ionicons';
import { ellipsisHorizontal, trashOutline } from 'ionicons/icons';
-import { get } from 'lodash';
+import { get } from 'lodash-es';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
import { catchError, filter, of } from 'rxjs';
diff --git a/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts
index 4a16c1a4e2..8c3ec2206e 100644
--- a/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts
+++ b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts
@@ -82,7 +82,7 @@ import {
swapVerticalOutline,
walletOutline
} from 'ionicons/icons';
-import { isNumber, round, uniqBy } from 'lodash';
+import { isNumber, round, uniqBy } from 'lodash-es';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
import { filter, switchMap } from 'rxjs/operators';
diff --git a/apps/client/src/app/components/portfolio-performance/portfolio-performance.component.ts b/apps/client/src/app/components/portfolio-performance/portfolio-performance.component.ts
index a48d77a2dd..4f7f35dd1e 100644
--- a/apps/client/src/app/components/portfolio-performance/portfolio-performance.component.ts
+++ b/apps/client/src/app/components/portfolio-performance/portfolio-performance.component.ts
@@ -23,7 +23,7 @@ import { IonIcon } from '@ionic/angular/standalone';
import { CountUp } from 'countup.js';
import { addIcons } from 'ionicons';
import { timeOutline } from 'ionicons/icons';
-import { isNumber } from 'lodash';
+import { isNumber } from 'lodash-es';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
@Component({
diff --git a/apps/client/src/app/components/portfolio-summary/portfolio-summary.component.ts b/apps/client/src/app/components/portfolio-summary/portfolio-summary.component.ts
index feedddacae..13686624f7 100644
--- a/apps/client/src/app/components/portfolio-summary/portfolio-summary.component.ts
+++ b/apps/client/src/app/components/portfolio-summary/portfolio-summary.component.ts
@@ -23,7 +23,7 @@ import {
ellipsisHorizontalCircleOutline,
informationCircleOutline
} from 'ionicons/icons';
-import { isNumber } from 'lodash';
+import { isNumber } from 'lodash-es';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
@Component({
diff --git a/apps/client/src/app/pages/api/api-page.component.ts b/apps/client/src/app/pages/api/api-page.component.ts
index 8b80370e8d..36c780a04e 100644
--- a/apps/client/src/app/pages/api/api-page.component.ts
+++ b/apps/client/src/app/pages/api/api-page.component.ts
@@ -32,7 +32,7 @@ import {
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { MatCardModule } from '@angular/material/card';
import { format, startOfYear } from 'date-fns';
-import { isObject } from 'lodash';
+import { isObject } from 'lodash-es';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
import { catchError, map, Observable, of, OperatorFunction } from 'rxjs';
diff --git a/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts b/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts
index 26255f2061..a1a9bddd6b 100644
--- a/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts
+++ b/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts
@@ -51,7 +51,7 @@ import { MatTableDataSource } from '@angular/material/table';
import { IonIcon } from '@ionic/angular/standalone';
import { addIcons } from 'ionicons';
import { cloudUploadOutline, warningOutline } from 'ionicons/icons';
-import { isArray, sortBy } from 'lodash';
+import { isArray, sortBy } from 'lodash-es';
import ms from 'ms';
import { ImportStep } from './enums/import-step';
diff --git a/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts b/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts
index 6d67b0253c..487bcf6086 100644
--- a/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts
+++ b/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts
@@ -53,7 +53,7 @@ import { MatCardModule } from '@angular/material/card';
import { MatDialog } from '@angular/material/dialog';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { ActivatedRoute, Router } from '@angular/router';
-import { isNumber } from 'lodash';
+import { isNumber } from 'lodash-es';
import { DeviceDetectorService } from 'ngx-device-detector';
import { filter, switchMap, tap } from 'rxjs';
diff --git a/apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts b/apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts
index 898c7f51a8..880753cce5 100644
--- a/apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts
+++ b/apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts
@@ -51,7 +51,7 @@ import { RouterModule } from '@angular/router';
import { IonIcon } from '@ionic/angular/standalone';
import { addIcons } from 'ionicons';
import { copyOutline, ellipsisVertical } from 'ionicons/icons';
-import { isNumber, keyBy, sortBy, union } from 'lodash';
+import { isNumber, keyBy, sortBy, union } from 'lodash-es';
import ms from 'ms';
import { DeviceDetectorService } from 'ngx-device-detector';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
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 dc787ae26a..33b877d42f 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
@@ -27,7 +27,7 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { FormControl } from '@angular/forms';
import { Big } from 'big.js';
-import { isFinite, isNumber } from 'lodash';
+import { isFinite, isNumber } from 'lodash-es';
import { DeviceDetectorService } from 'ngx-device-detector';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
diff --git a/apps/client/src/app/services/import-activities.service.ts b/apps/client/src/app/services/import-activities.service.ts
index 599c66551a..e302f00f9b 100644
--- a/apps/client/src/app/services/import-activities.service.ts
+++ b/apps/client/src/app/services/import-activities.service.ts
@@ -16,7 +16,7 @@ import { DataSource, Type as ActivityType } from '@ghostfolio/prisma/enums';
import { HttpClient } from '@angular/common/http';
import { inject, Service } from '@angular/core';
-import { isFinite, isNumber, isString } from 'lodash';
+import { isFinite, isNumber, isString } from 'lodash-es';
import { parse as csvToJson } from 'papaparse';
import { firstValueFrom } from 'rxjs';
import { v4 as uuidv4 } from 'uuid';
diff --git a/eslint.config.cjs b/eslint.config.cjs
index ee5e0a557c..282e1b548c 100644
--- a/eslint.config.cjs
+++ b/eslint.config.cjs
@@ -116,7 +116,24 @@ module.exports = [
'no-eval': 'error',
'no-fallthrough': 'error',
'no-new-wrappers': 'error',
- 'no-restricted-imports': ['error', 'rxjs/Rx'],
+ 'no-restricted-imports': [
+ 'error',
+ {
+ paths: [
+ 'rxjs/Rx',
+ {
+ message: 'Import from lodash-es instead.',
+ name: 'lodash'
+ }
+ ],
+ patterns: [
+ {
+ group: ['lodash/*'],
+ message: 'Import from lodash-es instead.'
+ }
+ ]
+ }
+ ],
'no-undef-init': 'error',
'no-underscore-dangle': 'off',
'no-var': 'error',
diff --git a/jest.preset.js b/jest.preset.js
index f078ddcec1..3cdca44c04 100644
--- a/jest.preset.js
+++ b/jest.preset.js
@@ -1,3 +1,6 @@
const nxPreset = require('@nx/jest/preset').default;
-module.exports = { ...nxPreset };
+module.exports = {
+ ...nxPreset,
+ transformIgnorePatterns: ['node_modules/(?!lodash-es/)']
+};
diff --git a/libs/common/src/lib/calculation-helper.ts b/libs/common/src/lib/calculation-helper.ts
index d49663d6cf..2cc1fa2d1c 100644
--- a/libs/common/src/lib/calculation-helper.ts
+++ b/libs/common/src/lib/calculation-helper.ts
@@ -10,7 +10,7 @@ import {
subMilliseconds,
subYears
} from 'date-fns';
-import { isFinite, isNumber } from 'lodash';
+import { isFinite, isNumber } from 'lodash-es';
import { resetHours } from './helper';
import { DateRange } from './types';
diff --git a/libs/common/src/lib/dtos/create-account.dto.ts b/libs/common/src/lib/dtos/create-account.dto.ts
index 92879dbb2c..5c1275db76 100644
--- a/libs/common/src/lib/dtos/create-account.dto.ts
+++ b/libs/common/src/lib/dtos/create-account.dto.ts
@@ -11,7 +11,7 @@ import {
MaxLength,
ValidateIf
} from 'class-validator';
-import { isString } from 'lodash';
+import { isString } from 'lodash-es';
export class CreateAccountDto {
/**
diff --git a/libs/common/src/lib/dtos/create-order.dto.ts b/libs/common/src/lib/dtos/create-order.dto.ts
index fd062b610b..d473e8e7b6 100644
--- a/libs/common/src/lib/dtos/create-order.dto.ts
+++ b/libs/common/src/lib/dtos/create-order.dto.ts
@@ -25,7 +25,7 @@ import {
Min,
Validate
} from 'class-validator';
-import { isString } from 'lodash';
+import { isString } from 'lodash-es';
export class CreateOrderDto {
@IsOptional()
diff --git a/libs/common/src/lib/dtos/update-account.dto.ts b/libs/common/src/lib/dtos/update-account.dto.ts
index deec684aae..c5f9f34679 100644
--- a/libs/common/src/lib/dtos/update-account.dto.ts
+++ b/libs/common/src/lib/dtos/update-account.dto.ts
@@ -11,7 +11,7 @@ import {
MaxLength,
ValidateIf
} from 'class-validator';
-import { isString } from 'lodash';
+import { isString } from 'lodash-es';
export class UpdateAccountDto {
/**
diff --git a/libs/common/src/lib/dtos/update-order.dto.ts b/libs/common/src/lib/dtos/update-order.dto.ts
index 0e381f2069..f7dfb0df53 100644
--- a/libs/common/src/lib/dtos/update-order.dto.ts
+++ b/libs/common/src/lib/dtos/update-order.dto.ts
@@ -24,7 +24,7 @@ import {
Min,
Validate
} from 'class-validator';
-import { isString } from 'lodash';
+import { isString } from 'lodash-es';
export class UpdateOrderDto {
@IsOptional()
diff --git a/libs/common/src/lib/helper.ts b/libs/common/src/lib/helper.ts
index 69764935d1..e10ca215cc 100644
--- a/libs/common/src/lib/helper.ts
+++ b/libs/common/src/lib/helper.ts
@@ -42,7 +42,7 @@ import {
uk,
zhCN
} from 'date-fns/locale';
-import { get, isNil, isString } from 'lodash';
+import { get, isNil, isString } from 'lodash-es';
import {
DEFAULT_CURRENCY,
diff --git a/libs/ui/jest.config.ts b/libs/ui/jest.config.ts
index 116fe6d54f..f9b97fe1a4 100644
--- a/libs/ui/jest.config.ts
+++ b/libs/ui/jest.config.ts
@@ -14,7 +14,7 @@ export default {
}
]
},
- transformIgnorePatterns: ['node_modules/(?!.*.mjs$)'],
+ transformIgnorePatterns: ['node_modules/(?!.*.mjs$|lodash-es/)'],
snapshotSerializers: [
'jest-preset-angular/build/serializers/no-ng-attributes',
'jest-preset-angular/build/serializers/ng-snapshot',
diff --git a/libs/ui/src/lib/account-balances/account-balances.component.ts b/libs/ui/src/lib/account-balances/account-balances.component.ts
index e27d29516c..04f13ccf10 100644
--- a/libs/ui/src/lib/account-balances/account-balances.component.ts
+++ b/libs/ui/src/lib/account-balances/account-balances.component.ts
@@ -40,7 +40,7 @@ import {
ellipsisHorizontal,
trashOutline
} from 'ionicons/icons';
-import { get, isNil } from 'lodash';
+import { get, isNil } from 'lodash-es';
import { GfValueComponent } from '../value';
diff --git a/libs/ui/src/lib/activities-filter/activities-filter.component.ts b/libs/ui/src/lib/activities-filter/activities-filter.component.ts
index 7a434d94c8..b6af11b0dc 100644
--- a/libs/ui/src/lib/activities-filter/activities-filter.component.ts
+++ b/libs/ui/src/lib/activities-filter/activities-filter.component.ts
@@ -29,7 +29,7 @@ import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { IonIcon } from '@ionic/angular/standalone';
import { addIcons } from 'ionicons';
import { closeOutline, searchOutline } from 'ionicons/icons';
-import { groupBy } from 'lodash';
+import { groupBy } from 'lodash-es';
import { BehaviorSubject } from 'rxjs';
import { translate } from '../i18n';
diff --git a/libs/ui/src/lib/assistant/assistant.component.ts b/libs/ui/src/lib/assistant/assistant.component.ts
index 6babc10398..ef0fb554ce 100644
--- a/libs/ui/src/lib/assistant/assistant.component.ts
+++ b/libs/ui/src/lib/assistant/assistant.component.ts
@@ -39,7 +39,7 @@ import {
closeOutline,
searchOutline
} from 'ionicons/icons';
-import { isFunction, sample } from 'lodash';
+import { isFunction, sample } from 'lodash-es';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
import { EMPTY, Observable, merge, of } from 'rxjs';
import {
diff --git a/libs/ui/src/lib/benchmark/benchmark.component.ts b/libs/ui/src/lib/benchmark/benchmark.component.ts
index 4f76978d45..bee06e3265 100644
--- a/libs/ui/src/lib/benchmark/benchmark.component.ts
+++ b/libs/ui/src/lib/benchmark/benchmark.component.ts
@@ -33,7 +33,7 @@ import { ActivatedRoute, Router, RouterModule } from '@angular/router';
import { IonIcon } from '@ionic/angular/standalone';
import { addIcons } from 'ionicons';
import { ellipsisHorizontal, trashOutline } from 'ionicons/icons';
-import { isNumber, round } from 'lodash';
+import { isNumber, round } from 'lodash-es';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
import { GfEntityLogoComponent } from '../entity-logo/entity-logo.component';
diff --git a/libs/ui/src/lib/fire-calculator/fire-calculator.component.ts b/libs/ui/src/lib/fire-calculator/fire-calculator.component.ts
index cfa2ee5cd2..959e934553 100644
--- a/libs/ui/src/lib/fire-calculator/fire-calculator.component.ts
+++ b/libs/ui/src/lib/fire-calculator/fire-calculator.component.ts
@@ -59,7 +59,7 @@ import {
} from 'date-fns';
import { addIcons } from 'ionicons';
import { calendarClearOutline } from 'ionicons/icons';
-import { isNumber } from 'lodash';
+import { isNumber } from 'lodash-es';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
import { debounceTime } from 'rxjs';
diff --git a/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.component.ts b/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.component.ts
index 17a5a5e612..4f8540974f 100644
--- a/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.component.ts
+++ b/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.component.ts
@@ -29,7 +29,7 @@ import { MatInputModule } from '@angular/material/input';
import { IonIcon } from '@ionic/angular/standalone';
import { addIcons } from 'ionicons';
import { calendarClearOutline, refreshOutline } from 'ionicons/icons';
-import { isNil } from 'lodash';
+import { isNil } from 'lodash-es';
import { HistoricalMarketDataEditorDialogParams } from './interfaces/interfaces';
diff --git a/libs/ui/src/lib/notifications/notification.service.ts b/libs/ui/src/lib/notifications/notification.service.ts
index 4eb1cbd54d..d1c8459a6c 100644
--- a/libs/ui/src/lib/notifications/notification.service.ts
+++ b/libs/ui/src/lib/notifications/notification.service.ts
@@ -3,7 +3,7 @@ import { translate } from '@ghostfolio/ui/i18n';
import { inject, Service } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
-import { isFunction } from 'lodash';
+import { isFunction } from 'lodash-es';
import { GfAlertDialogComponent } from './alert-dialog/alert-dialog.component';
import { GfConfirmationDialogComponent } from './confirmation-dialog/confirmation-dialog.component';
diff --git a/libs/ui/src/lib/services/admin.service.ts b/libs/ui/src/lib/services/admin.service.ts
index aba02c9b3f..0307909dc1 100644
--- a/libs/ui/src/lib/services/admin.service.ts
+++ b/libs/ui/src/lib/services/admin.service.ts
@@ -31,7 +31,7 @@ import { GF_ENVIRONMENT } from '@ghostfolio/ui/environment';
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
import { inject, Service } from '@angular/core';
import { JobStatus } from 'bull';
-import { isNumber } from 'lodash';
+import { isNumber } from 'lodash-es';
@Service()
export class AdminService {
diff --git a/libs/ui/src/lib/services/data.service.ts b/libs/ui/src/lib/services/data.service.ts
index 9bbb0bf5fa..8545c36ba4 100644
--- a/libs/ui/src/lib/services/data.service.ts
+++ b/libs/ui/src/lib/services/data.service.ts
@@ -82,7 +82,7 @@ import { inject, Service } from '@angular/core';
import { SortDirection } from '@angular/material/sort';
import { utc } from '@date-fns/utc';
import { format, parseISO } from 'date-fns';
-import { cloneDeep, groupBy, isNumber } from 'lodash';
+import { cloneDeep, groupBy, isNumber } from 'lodash-es';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
diff --git a/libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.ts b/libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.ts
index 683f7c9252..d56aa177bf 100644
--- a/libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.ts
+++ b/libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.ts
@@ -36,7 +36,7 @@ import {
} from '@angular/material/form-field';
import { MatInput, MatInputModule } from '@angular/material/input';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
-import { isString } from 'lodash';
+import { isString } from 'lodash-es';
import { tap } from 'rxjs';
import {
debounceTime,
diff --git a/libs/ui/src/lib/top-holdings/top-holdings.component.ts b/libs/ui/src/lib/top-holdings/top-holdings.component.ts
index 03e7f6cc46..1a675a5531 100644
--- a/libs/ui/src/lib/top-holdings/top-holdings.component.ts
+++ b/libs/ui/src/lib/top-holdings/top-holdings.component.ts
@@ -18,7 +18,7 @@ import {
import { MatButtonModule } from '@angular/material/button';
import { MatPaginator, MatPaginatorModule } from '@angular/material/paginator';
import { MatTableDataSource, MatTableModule } from '@angular/material/table';
-import { capitalize } from 'lodash';
+import { capitalize } from 'lodash-es';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
import { GfValueComponent } from '../value/value.component';
diff --git a/libs/ui/src/lib/treemap-chart/treemap-chart.component.ts b/libs/ui/src/lib/treemap-chart/treemap-chart.component.ts
index 54c8d7f907..a35d3deef5 100644
--- a/libs/ui/src/lib/treemap-chart/treemap-chart.component.ts
+++ b/libs/ui/src/lib/treemap-chart/treemap-chart.component.ts
@@ -32,7 +32,7 @@ import { Chart, LinearScale, Tooltip } from 'chart.js';
import { TreemapController, TreemapElement } from 'chartjs-chart-treemap';
import { isUUID } from 'class-validator';
import { differenceInDays, max } from 'date-fns';
-import { orderBy, round } from 'lodash';
+import { orderBy, round } from 'lodash-es';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
import OpenColor from 'open-color';
diff --git a/libs/ui/src/lib/value/value.component.ts b/libs/ui/src/lib/value/value.component.ts
index c2c7d4621d..4101b4066f 100644
--- a/libs/ui/src/lib/value/value.component.ts
+++ b/libs/ui/src/lib/value/value.component.ts
@@ -21,7 +21,7 @@ import { MatSnackBar } from '@angular/material/snack-bar';
import { IonIcon } from '@ionic/angular/standalone';
import { addIcons } from 'ionicons';
import { checkmarkOutline, copyOutline } from 'ionicons/icons';
-import { isNumber } from 'lodash';
+import { isNumber } from 'lodash-es';
import ms from 'ms';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
diff --git a/package-lock.json b/package-lock.json
index 3f8ee0f880..c3ee51dc53 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -83,7 +83,7 @@
"ionicons": "8.0.13",
"jsonpath": "1.3.0",
"katex": "0.16.47",
- "lodash": "4.18.1",
+ "lodash-es": "4.18.1",
"marked": "18.0.12",
"marked-katex-extension": "5.1.12",
"ms": "3.0.0-canary.1",
@@ -147,7 +147,7 @@
"@types/google-spreadsheet": "3.1.5",
"@types/jest": "30.0.0",
"@types/jsonpath": "0.2.4",
- "@types/lodash": "4.17.25",
+ "@types/lodash-es": "4.17.12",
"@types/node": "22.20.1",
"@types/papaparse": "5.5.2",
"@types/passport-google-oauth20": "2.0.17",
@@ -15221,6 +15221,16 @@
"devOptional": true,
"license": "MIT"
},
+ "node_modules/@types/lodash-es": {
+ "version": "4.17.12",
+ "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz",
+ "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/lodash": "*"
+ }
+ },
"node_modules/@types/luxon": {
"version": "3.7.1",
"resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.7.1.tgz",
@@ -27042,7 +27052,6 @@
"version": "4.18.1",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz",
"integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==",
- "dev": true,
"license": "MIT"
},
"node_modules/lodash.debounce": {
diff --git a/package.json b/package.json
index a3f94b74b6..ca4f3ebcff 100644
--- a/package.json
+++ b/package.json
@@ -127,7 +127,7 @@
"ionicons": "8.0.13",
"jsonpath": "1.3.0",
"katex": "0.16.47",
- "lodash": "4.18.1",
+ "lodash-es": "4.18.1",
"marked": "18.0.12",
"marked-katex-extension": "5.1.12",
"ms": "3.0.0-canary.1",
@@ -191,7 +191,7 @@
"@types/google-spreadsheet": "3.1.5",
"@types/jest": "30.0.0",
"@types/jsonpath": "0.2.4",
- "@types/lodash": "4.17.25",
+ "@types/lodash-es": "4.17.12",
"@types/node": "22.20.1",
"@types/papaparse": "5.5.2",
"@types/passport-google-oauth20": "2.0.17",
From d85772d4034bcacdd6af686276842b382456bd91 Mon Sep 17 00:00:00 2001
From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
Date: Wed, 30 Sep 2026 12:13:31 +0200
Subject: [PATCH 14/27] Bugfix/allocation column of top holdings (#7974)
* Fix allocation column of top holdings of ETFs
* Update changelog
---
CHANGELOG.md | 3 ++-
.../allocations/allocations-page.component.ts | 21 +++++++++----------
2 files changed, 12 insertions(+), 12 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c9937fcb31..5ae63148f2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -20,7 +20,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
- Fixed the colors of the investment chart component (dark mode)
-- Fixed the value column of the top holdings on the allocations page in the _Presenter View_
+- 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 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/allocations/allocations-page.component.ts b/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts
index 487bcf6086..647bc761e3 100644
--- a/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts
+++ b/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts
@@ -134,11 +134,6 @@ export class GfAllocationsPageComponent implements OnInit {
protected readonly UNKNOWN_KEY = UNKNOWN_KEY;
protected user: User;
- private topHoldingsMap: {
- [name: string]: { name: string; value: number };
- };
- private totalValueInEtf = 0;
-
private readonly changeDetectorRef = inject(ChangeDetectorRef);
private readonly dataService = inject(DataService);
private readonly destroyRef = inject(DestroyRef);
@@ -365,7 +360,6 @@ export class GfAllocationsPageComponent implements OnInit {
value: 0
}
};
- this.topHoldingsMap = {};
}
private initializeAllocationsData() {
@@ -388,7 +382,12 @@ export class GfAllocationsPageComponent implements OnInit {
};
}
+ const topHoldingsMap: {
+ [name: string]: { name: string; value: number };
+ } = {};
+
let totalValueExcludingCashPositions = 0;
+ let totalValueInEtf = 0;
for (const position of this.portfolioDetails.holdings) {
const assetProfileIdentifier = getAssetProfileIdentifier(
@@ -474,12 +473,12 @@ export class GfAllocationsPageComponent implements OnInit {
? valueInBaseCurrency
: allocationInPercentage * (position.valueInPercentage ?? 0);
- const holdingData = this.topHoldingsMap[normalizedAssetName];
+ const holdingData = topHoldingsMap[normalizedAssetName];
if (holdingData) {
holdingData.value += value;
} else {
- this.topHoldingsMap[normalizedAssetName] = {
+ topHoldingsMap[normalizedAssetName] = {
name,
value
};
@@ -515,7 +514,7 @@ export class GfAllocationsPageComponent implements OnInit {
}
if (this.holdings[assetProfileIdentifier].assetSubClass === 'ETF') {
- this.totalValueInEtf += this.holdings[assetProfileIdentifier].value;
+ totalValueInEtf += this.holdings[assetProfileIdentifier].value;
}
const symbol = position.assetProfile.symbol;
@@ -582,7 +581,7 @@ export class GfAllocationsPageComponent implements OnInit {
};
}
- this.topHoldings = Object.values(this.topHoldingsMap)
+ this.topHoldings = Object.values(topHoldingsMap)
.map(({ name, value }): HoldingWithParents => {
if (this.showValuesInPercentage()) {
return {
@@ -594,7 +593,7 @@ export class GfAllocationsPageComponent implements OnInit {
return {
name,
allocationInPercentage:
- this.totalValueInEtf > 0 ? value / this.totalValueInEtf : 0,
+ totalValueInEtf > 0 ? value / totalValueInEtf : 0,
parents: this.portfolioDetails.holdings
.map((holding) => {
if (holding.assetProfile.holdings.length > 0) {
From 435aeeb1f7acd164907103aca979aa50e43e1cc3 Mon Sep 17 00:00:00 2001
From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
Date: Wed, 30 Sep 2026 16:25:49 +0200
Subject: [PATCH 15/27] Bugfix/light color scheme in chart helpers (#7975)
* Fix colors of charts if appearance is set to light and OS uses dark mode
* Update changelog
---
CHANGELOG.md | 1 +
apps/client/src/app/app.component.ts | 8 ++---
.../asset-profile-dialog-host.component.ts | 7 ++--
.../interfaces/interfaces.ts | 2 +-
.../investment-chart.component.ts | 22 +++++-------
libs/common/src/lib/chart-helper.ts | 14 ++++----
libs/common/src/lib/config.ts | 3 +-
libs/common/src/lib/helper.spec.ts | 35 +++++++++++++++++++
libs/common/src/lib/helper.ts | 20 ++++++-----
libs/ui/src/lib/chart/chart.options.ts | 2 +-
.../lib/line-chart/line-chart.component.ts | 8 ++---
.../portfolio-proportion-chart.component.ts | 6 ++--
.../treemap-chart.component.stories.ts | 4 +--
13 files changed, 76 insertions(+), 56 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5ae63148f2..be16617266 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
- Fixed the colors of the investment chart component (dark mode)
+- Fixed the colors of the charts if the appearance is set to light and the operating system uses dark mode
- 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)
diff --git a/apps/client/src/app/app.component.ts b/apps/client/src/app/app.component.ts
index 7252ad1646..a1f8ebfe9c 100644
--- a/apps/client/src/app/app.component.ts
+++ b/apps/client/src/app/app.component.ts
@@ -1,4 +1,4 @@
-import { getCssVariable } from '@ghostfolio/common/helper';
+import { getCssVariable, isDarkColorScheme } from '@ghostfolio/common/helper';
import {
AssetProfileIdentifier,
InfoItem,
@@ -260,11 +260,7 @@ export class GfAppComponent implements OnInit {
}
private initializeTheme(userPreferredColorScheme?: ColorScheme) {
- const isDarkTheme = userPreferredColorScheme
- ? userPreferredColorScheme === 'DARK'
- : window.matchMedia('(prefers-color-scheme: dark)').matches;
-
- this.toggleTheme(isDarkTheme);
+ this.toggleTheme(isDarkColorScheme(userPreferredColorScheme));
// Default chart styles
Chart.defaults.font.family = getCssVariable('--font-family-sans-serif');
diff --git a/apps/client/src/app/components/admin-market-data/asset-profile-dialog-host/asset-profile-dialog-host.component.ts b/apps/client/src/app/components/admin-market-data/asset-profile-dialog-host/asset-profile-dialog-host.component.ts
index af83ef4a7d..26af45e20b 100644
--- a/apps/client/src/app/components/admin-market-data/asset-profile-dialog-host/asset-profile-dialog-host.component.ts
+++ b/apps/client/src/app/components/admin-market-data/asset-profile-dialog-host/asset-profile-dialog-host.component.ts
@@ -1,8 +1,5 @@
import { UserService } from '@ghostfolio/client/services/user/user.service';
-import {
- DEFAULT_COLOR_SCHEME,
- DEFAULT_LOCALE
-} from '@ghostfolio/common/config';
+import { DEFAULT_LOCALE } from '@ghostfolio/common/config';
import { AssetProfileIdentifier, User } from '@ghostfolio/common/interfaces';
import { internalRoutes } from '@ghostfolio/common/routes/routes';
import { DataSource } from '@ghostfolio/prisma/enums';
@@ -150,7 +147,7 @@ export class GfAssetProfileDialogHostComponent implements OnDestroy, OnInit {
data: {
dataSource,
symbol,
- colorScheme: user?.settings.colorScheme ?? DEFAULT_COLOR_SCHEME,
+ colorScheme: user?.settings?.colorScheme,
deviceType: this.deviceType(),
locale: user?.settings?.locale ?? DEFAULT_LOCALE
} satisfies AssetProfileDialogParams,
diff --git a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/interfaces/interfaces.ts b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/interfaces/interfaces.ts
index 0f000364ee..2e2bb7b118 100644
--- a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/interfaces/interfaces.ts
+++ b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/interfaces/interfaces.ts
@@ -2,7 +2,7 @@ import { ColorScheme } from '@ghostfolio/common/types';
import { DataSource } from '@ghostfolio/prisma/enums';
export interface AssetProfileDialogParams {
- colorScheme: ColorScheme;
+ colorScheme?: ColorScheme;
dataSource: DataSource;
deviceType: string;
locale: string;
diff --git a/apps/client/src/app/components/investment-chart/investment-chart.component.ts b/apps/client/src/app/components/investment-chart/investment-chart.component.ts
index f51fc4d641..b3cc0d753e 100644
--- a/apps/client/src/app/components/investment-chart/investment-chart.component.ts
+++ b/apps/client/src/app/components/investment-chart/investment-chart.component.ts
@@ -7,11 +7,7 @@ import {
getZeroLineAnnotation,
transformTickToAbbreviation
} from '@ghostfolio/common/chart-helper';
-import {
- DEFAULT_COLOR_SCHEME,
- primaryColorRgb,
- secondaryColorRgb
-} from '@ghostfolio/common/config';
+import { primaryColorRgb, secondaryColorRgb } from '@ghostfolio/common/config';
import { getLocale, parseDate } from '@ghostfolio/common/helper';
import { LineChartItem } from '@ghostfolio/common/interfaces';
import { InvestmentItem } from '@ghostfolio/common/interfaces/investment-item.interface';
@@ -174,15 +170,13 @@ export class GfInvestmentChartComponent implements OnChanges, OnDestroy {
this.chart.update();
} else {
- const colorScheme = this.colorScheme ?? DEFAULT_COLOR_SCHEME;
-
this.chart = new Chart<'bar' | 'line'>(
this.chartCanvas().nativeElement,
{
data: chartData,
options: {
animation: false,
- elements: getChartElementsOptions(colorScheme),
+ elements: getChartElementsOptions(this.colorScheme),
interaction: { intersect: false, mode: 'index' },
maintainAspectRatio: true,
plugins: {
@@ -210,7 +204,7 @@ export class GfInvestmentChartComponent implements OnChanges, OnDestroy {
value: this.savingsRate
}
: undefined,
- yAxis: getZeroLineAnnotation(colorScheme)
+ yAxis: getZeroLineAnnotation(this.colorScheme)
}
},
legend: {
@@ -218,18 +212,18 @@ export class GfInvestmentChartComponent implements OnChanges, OnDestroy {
},
tooltip: this.getTooltipPluginConfiguration(),
verticalHoverLine: {
- color: getChartBorderColor(colorScheme)
+ color: getChartBorderColor(this.colorScheme)
}
},
responsive: true,
scales: {
x: getTimeAxisOptions({
- colorScheme,
borderWidth: this.groupBy ? 0 : 1,
+ colorScheme: this.colorScheme,
locale: this.locale
}),
y: getValueAxisOptions({
- colorScheme,
+ colorScheme: this.colorScheme,
display: !this.isInPercentage,
tickCallback: (tickValue) => {
return transformTickToAbbreviation(Number(tickValue));
@@ -238,7 +232,7 @@ export class GfInvestmentChartComponent implements OnChanges, OnDestroy {
}
},
plugins: [
- getVerticalHoverLinePlugin(this.chartCanvas(), colorScheme)
+ getVerticalHoverLinePlugin(this.chartCanvas(), this.colorScheme)
],
type: this.groupBy ? 'bar' : 'line'
}
@@ -251,7 +245,7 @@ export class GfInvestmentChartComponent implements OnChanges, OnDestroy {
TooltipOptions<'bar' | 'line'>
> {
return getTimeSeriesTooltipOptions<'bar' | 'line'>({
- colorScheme: this.colorScheme ?? DEFAULT_COLOR_SCHEME,
+ colorScheme: this.colorScheme,
currency: this.isInPercentage ? undefined : this.currency,
groupBy: this.groupBy,
locale: this.isInPercentage ? undefined : this.locale,
diff --git a/libs/common/src/lib/chart-helper.ts b/libs/common/src/lib/chart-helper.ts
index 23a00ca973..58f370d06d 100644
--- a/libs/common/src/lib/chart-helper.ts
+++ b/libs/common/src/lib/chart-helper.ts
@@ -41,12 +41,12 @@ export function formatGroupedDate({
return format(date, DATE_FORMAT);
}
-export function getChartBorderColor(colorScheme: ColorScheme) {
+export function getChartBorderColor(colorScheme?: ColorScheme) {
return `rgba(${getTextColor(colorScheme)}, 0.1)`;
}
export function getChartElementsOptions(
- colorScheme: ColorScheme
+ colorScheme?: ColorScheme
): ChartOptions<'bar' | 'line'>['elements'] {
return {
line: {
@@ -67,7 +67,7 @@ export function getTimeAxisOptions({
locale = getLocale()
}: {
borderWidth?: number;
- colorScheme: ColorScheme;
+ colorScheme?: ColorScheme;
display?: boolean;
locale?: string;
}): ScaleOptions<'time'> {
@@ -95,7 +95,7 @@ export function getTooltipOptions({
locale = getLocale(),
unit = ''
}: {
- colorScheme: ColorScheme;
+ colorScheme?: ColorScheme;
currency?: string;
groupBy?: GroupBy;
locale?: string;
@@ -174,7 +174,7 @@ export function getValueAxisOptions({
highlightedValues = [],
tickCallback
}: {
- colorScheme: ColorScheme;
+ colorScheme?: ColorScheme;
display?: boolean;
highlightedValues?: number[];
tickCallback: (
@@ -214,7 +214,7 @@ export function getValueAxisOptions({
export function getVerticalHoverLinePlugin(
chartCanvas: ElementRef,
- colorScheme: ColorScheme
+ colorScheme?: ColorScheme
): Plugin {
return {
afterDatasetsDraw: (chart, _, options) => {
@@ -249,7 +249,7 @@ export function getVerticalHoverLinePlugin(
}
export function getZeroLineAnnotation(
- colorScheme: ColorScheme
+ colorScheme?: ColorScheme
): AnnotationOptions<'line'> {
return {
borderColor: getChartBorderColor(colorScheme),
diff --git a/libs/common/src/lib/config.ts b/libs/common/src/lib/config.ts
index 03d36f577b..6ff492f671 100644
--- a/libs/common/src/lib/config.ts
+++ b/libs/common/src/lib/config.ts
@@ -8,7 +8,7 @@ import {
import { JobOptions, JobStatus } from 'bull';
import ms from 'ms';
-import { ColorScheme, DateRange } from './types';
+import { DateRange } from './types';
export const ghostfolioPrefix = 'GF';
@@ -100,7 +100,6 @@ export const PORTFOLIO_SNAPSHOT_COMPUTATION_QUEUE_PRIORITY_LOW =
export const STATISTICS_GATHERING_QUEUE = 'STATISTICS_GATHERING_QUEUE';
-export const DEFAULT_COLOR_SCHEME: ColorScheme = 'LIGHT';
export const DEFAULT_CURRENCY = 'USD';
export const DEFAULT_DATE_FORMAT_MONTH_YEAR = 'MMM yyyy';
export const DEFAULT_DATE_RANGE: DateRange = 'max';
diff --git a/libs/common/src/lib/helper.spec.ts b/libs/common/src/lib/helper.spec.ts
index d1130f0d75..b7e7635670 100644
--- a/libs/common/src/lib/helper.spec.ts
+++ b/libs/common/src/lib/helper.spec.ts
@@ -13,6 +13,7 @@ import {
isAccountExcluded,
isCurrency,
isCurrencySymbol,
+ isDarkColorScheme,
isSplitRatio,
isValidCurrencyCode,
isValidCustomAssetProfileSymbol,
@@ -348,6 +349,40 @@ describe('Helper', () => {
});
});
+ describe('Is dark color scheme', () => {
+ const mockPrefersColorSchemeDark = (matches: boolean) => {
+ window.matchMedia = jest.fn(() => {
+ return { matches } as MediaQueryList;
+ });
+ };
+
+ afterEach(() => {
+ Reflect.deleteProperty(window, 'matchMedia');
+ });
+
+ it('Dark color scheme with operating system in light mode', () => {
+ mockPrefersColorSchemeDark(false);
+ expect(isDarkColorScheme('DARK')).toEqual(true);
+ });
+
+ it('Light color scheme with operating system in dark mode', () => {
+ mockPrefersColorSchemeDark(true);
+ expect(isDarkColorScheme('LIGHT')).toEqual(false);
+ });
+
+ it('Auto color scheme with operating system in dark mode', () => {
+ mockPrefersColorSchemeDark(true);
+ expect(isDarkColorScheme()).toEqual(true);
+ expect(isDarkColorScheme(null)).toEqual(true);
+ });
+
+ it('Auto color scheme with operating system in light mode', () => {
+ mockPrefersColorSchemeDark(false);
+ expect(isDarkColorScheme()).toEqual(false);
+ expect(isDarkColorScheme(null)).toEqual(false);
+ });
+ });
+
describe('Is split ratio', () => {
it('Forward split', () => {
expect(isSplitRatio({ denominator: 1, numerator: 2 })).toEqual(true);
diff --git a/libs/common/src/lib/helper.ts b/libs/common/src/lib/helper.ts
index e10ca215cc..2a76261dae 100644
--- a/libs/common/src/lib/helper.ts
+++ b/libs/common/src/lib/helper.ts
@@ -345,12 +345,9 @@ export function getAssetProfileIdentifier({
return `${dataSource}-${symbol}`;
}
-export function getBackgroundColor(aColorScheme: ColorScheme) {
+export function getBackgroundColor(aColorScheme?: ColorScheme) {
return getCssVariable(
- aColorScheme === 'DARK' ||
- window.matchMedia('(prefers-color-scheme: dark)').matches
- ? '--dark-background'
- : '--light-background'
+ isDarkColorScheme(aColorScheme) ? '--dark-background' : '--light-background'
);
}
@@ -531,10 +528,9 @@ export function getSum(aArray: Big[]) {
return new Big(0);
}
-export function getTextColor(aColorScheme: ColorScheme) {
+export function getTextColor(aColorScheme?: ColorScheme) {
const cssVariable = getCssVariable(
- aColorScheme === 'DARK' ||
- window.matchMedia('(prefers-color-scheme: dark)').matches
+ isDarkColorScheme(aColorScheme)
? '--light-primary-text'
: '--dark-primary-text'
);
@@ -612,6 +608,14 @@ export function isCurrencySymbol(aSymbol: string) {
);
}
+export function isDarkColorScheme(aColorScheme?: ColorScheme | null) {
+ if (aColorScheme) {
+ return aColorScheme === 'DARK';
+ }
+
+ return window.matchMedia('(prefers-color-scheme: dark)').matches;
+}
+
export function isDerivedCurrency(aCurrency: string) {
if (aCurrency === 'USX') {
return true;
diff --git a/libs/ui/src/lib/chart/chart.options.ts b/libs/ui/src/lib/chart/chart.options.ts
index cd2802cfb3..277704b76f 100644
--- a/libs/ui/src/lib/chart/chart.options.ts
+++ b/libs/ui/src/lib/chart/chart.options.ts
@@ -12,7 +12,7 @@ export function getTimeSeriesTooltipOptions({
locale,
unit
}: {
- colorScheme: ColorScheme;
+ colorScheme?: ColorScheme;
currency?: string;
groupBy?: GroupBy;
locale?: string;
diff --git a/libs/ui/src/lib/line-chart/line-chart.component.ts b/libs/ui/src/lib/line-chart/line-chart.component.ts
index 5dbb6cc31c..49eae2f5df 100644
--- a/libs/ui/src/lib/line-chart/line-chart.component.ts
+++ b/libs/ui/src/lib/line-chart/line-chart.component.ts
@@ -4,11 +4,7 @@ import {
getValueAxisOptions,
getVerticalHoverLinePlugin
} from '@ghostfolio/common/chart-helper';
-import {
- DEFAULT_COLOR_SCHEME,
- primaryColorRgb,
- secondaryColorRgb
-} from '@ghostfolio/common/config';
+import { primaryColorRgb, secondaryColorRgb } from '@ghostfolio/common/config';
import { getBackgroundColor, getLocale } from '@ghostfolio/common/helper';
import { LineChartItem } from '@ghostfolio/common/interfaces';
import { ColorScheme } from '@ghostfolio/common/types';
@@ -56,7 +52,7 @@ export class GfLineChartComponent
{
@Input() benchmarkDataItems: LineChartItem[] = [];
@Input() benchmarkLabel = '';
- @Input() colorScheme: ColorScheme = DEFAULT_COLOR_SCHEME;
+ @Input() colorScheme?: ColorScheme;
@Input() currency: string;
@Input() historicalDataItems: LineChartItem[];
@Input() isAnimated = false;
diff --git a/libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts b/libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts
index e90c446b41..ff61830f04 100644
--- a/libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts
+++ b/libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts
@@ -1,5 +1,5 @@
import { getTooltipOptions } from '@ghostfolio/common/chart-helper';
-import { DEFAULT_COLOR_SCHEME, UNKNOWN_KEY } from '@ghostfolio/common/config';
+import { UNKNOWN_KEY } from '@ghostfolio/common/config';
import { getLocale, getSum, getTextColor } from '@ghostfolio/common/helper';
import { PortfolioPosition } from '@ghostfolio/common/interfaces';
import { ColorScheme } from '@ghostfolio/common/types';
@@ -124,7 +124,7 @@ export class GfPortfolioProportionChartComponent
};
} = {};
- const textColor = getTextColor(this.colorScheme ?? DEFAULT_COLOR_SCHEME);
+ const textColor = getTextColor(this.colorScheme);
this.colorMap = {
[this.OTHER_KEY]: `rgba(${textColor}, 0.24)`,
@@ -448,7 +448,7 @@ export class GfPortfolioProportionChartComponent
): Partial> {
return {
...getTooltipOptions({
- colorScheme: this.colorScheme ?? DEFAULT_COLOR_SCHEME,
+ colorScheme: this.colorScheme,
currency: this.baseCurrency,
locale: this.locale
}),
diff --git a/libs/ui/src/lib/treemap-chart/treemap-chart.component.stories.ts b/libs/ui/src/lib/treemap-chart/treemap-chart.component.stories.ts
index 04ea5c65f0..72f34e8027 100644
--- a/libs/ui/src/lib/treemap-chart/treemap-chart.component.stories.ts
+++ b/libs/ui/src/lib/treemap-chart/treemap-chart.component.stories.ts
@@ -1,5 +1,3 @@
-import { DEFAULT_COLOR_SCHEME } from '@ghostfolio/common/config';
-
import { CommonModule } from '@angular/common';
import { moduleMetadata } from '@storybook/angular';
import type { Meta, StoryObj } from '@storybook/angular';
@@ -38,7 +36,7 @@ export const Default: Story = {
args: {
holdings,
baseCurrency: 'USD',
- colorScheme: DEFAULT_COLOR_SCHEME,
+ colorScheme: 'LIGHT',
cursor: undefined,
dateRange: 'mtd',
locale: 'en-US'
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 16/27] 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 17/27] 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 18/27] 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 19/27] 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"
>
+
+
|
@@ -175,17 +183,21 @@