From ab7f1fd88141ecaa868dc14899fadba4701b5616 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 11 Oct 2025 11:25:28 +0200 Subject: [PATCH 01/37] Task/set up GitHub Sponsors (#5723) * Add github --- .github/FUNDING.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 9df3e0d6d..a881e8fc8 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1 +1,2 @@ buy_me_a_coffee: ghostfolio +github: ghostfolio From 948233c6510e4d53c304e9761d2c438669a4a22c Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 11 Oct 2025 11:26:10 +0200 Subject: [PATCH 02/37] Task/remove @IsOptional() from dataSource in CreateOrderDto (#5703) * Remove is @IsOptional() from dataSource --- apps/api/src/app/order/create-order.dto.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/api/src/app/order/create-order.dto.ts b/apps/api/src/app/order/create-order.dto.ts index af87fd93e..ba7a1d868 100644 --- a/apps/api/src/app/order/create-order.dto.ts +++ b/apps/api/src/app/order/create-order.dto.ts @@ -44,8 +44,7 @@ export class CreateOrderDto { customCurrency?: string; @IsEnum(DataSource) - @IsOptional() - dataSource?: DataSource; + dataSource: DataSource; @IsISO8601() @Validate(IsAfter1970Constraint) From c5c11929008ac5bf6c4e56bcf9fe337f3a99d4f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sven=20G=C3=BCnther?= Date: Sat, 11 Oct 2025 17:07:42 +0200 Subject: [PATCH 03/37] Bugfix/import of custom asset profiles (#5670) * Import of custom asset profiles * Update changelog --- CHANGELOG.md | 1 + apps/api/src/app/import/import.service.ts | 38 ++++++++++++++++ test/import/ok/penthouse-apartment.json | 53 +++++++++++++++++++++++ 3 files changed, 92 insertions(+) create mode 100644 test/import/ok/penthouse-apartment.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 58a7b3b48..c7472e421 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Fixed the server startup message to properly display IPv6 addresses +- Fixed an issue where importing custom asset profiles failed due to validation errors ## 2.207.0 - 2025-10-08 diff --git a/apps/api/src/app/import/import.service.ts b/apps/api/src/app/import/import.service.ts index 82231d237..69ec781c3 100644 --- a/apps/api/src/app/import/import.service.ts +++ b/apps/api/src/app/import/import.service.ts @@ -373,6 +373,7 @@ export class ImportService { const assetProfiles = await this.validateActivities({ activitiesDto, + assetProfilesWithMarketDataDto, maxActivitiesToImport, user }); @@ -698,10 +699,12 @@ export class ImportService { private async validateActivities({ activitiesDto, + assetProfilesWithMarketDataDto, maxActivitiesToImport, user }: { activitiesDto: Partial[]; + assetProfilesWithMarketDataDto: ImportDataDto['assetProfiles']; maxActivitiesToImport: number; user: UserWithSettings; }) { @@ -749,6 +752,41 @@ export class ImportService { )?.[symbol] }; + if (!assetProfile?.name) { + const assetProfileInImport = assetProfilesWithMarketDataDto?.find( + (profile) => { + return ( + profile.dataSource === dataSource && profile.symbol === symbol + ); + } + ); + + if (assetProfileInImport) { + // Merge all fields of custom asset profiles into the validation object + Object.assign(assetProfile, { + assetClass: assetProfileInImport.assetClass, + assetSubClass: assetProfileInImport.assetSubClass, + comment: assetProfileInImport.comment, + countries: assetProfileInImport.countries, + currency: assetProfileInImport.currency, + cusip: assetProfileInImport.cusip, + dataSource: assetProfileInImport.dataSource, + figi: assetProfileInImport.figi, + figiComposite: assetProfileInImport.figiComposite, + figiShareClass: assetProfileInImport.figiShareClass, + holdings: assetProfileInImport.holdings, + isActive: assetProfileInImport.isActive, + isin: assetProfileInImport.isin, + name: assetProfileInImport.name, + scraperConfiguration: assetProfileInImport.scraperConfiguration, + sectors: assetProfileInImport.sectors, + symbol: assetProfileInImport.symbol, + symbolMapping: assetProfileInImport.symbolMapping, + url: assetProfileInImport.url + }); + } + } + if ( (dataSource !== 'MANUAL' && type === 'BUY') || type === 'DIVIDEND' || diff --git a/test/import/ok/penthouse-apartment.json b/test/import/ok/penthouse-apartment.json new file mode 100644 index 000000000..3b230cf76 --- /dev/null +++ b/test/import/ok/penthouse-apartment.json @@ -0,0 +1,53 @@ +{ + "meta": { + "date": "2023-02-05T00:00:00.000Z", + "version": "dev" + }, + "accounts": [], + "assetProfiles": [ + { + "assetClass": null, + "assetSubClass": null, + "comment": null, + "countries": [], + "currency": "USD", + "cusip": null, + "dataSource": "MANUAL", + "figi": null, + "figiComposite": null, + "figiShareClass": null, + "holdings": [], + "isActive": true, + "isin": null, + "marketData": [], + "name": "Penthouse Apartment", + "scraperConfiguration": null, + "sectors": [], + "symbol": "7e91b7d4-1430-4212-8380-289a06c9bbc1", + "symbolMapping": {}, + "url": null + } + ], + "platforms": [], + "tags": [], + "activities": [ + { + "accountId": null, + "comment": null, + "currency": "USD", + "dataSource": "MANUAL", + "date": "2022-01-01T00:00:00.000Z", + "fee": 0, + "quantity": 1, + "symbol": "7e91b7d4-1430-4212-8380-289a06c9bbc1", + "tags": [], + "type": "BUY", + "unitPrice": 500000, + } + ], + "user": { + "settings": { + "currency": "USD" + } + } +} From 13838bed6d20307eccf3ea02cb632ea0e5642b5c Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 11 Oct 2025 20:12:37 +0200 Subject: [PATCH 04/37] Feature/improve language localization for de 20250811 (#5730) * Update translation * Update changelog --- CHANGELOG.md | 1 + apps/client/src/locales/messages.de.xlf | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c7472e421..1128a6205 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 - Changed the _As seen in_ section on the landing page to an animated carousel - Refactored various components to use self-closing tags +- Improved the language localization for German (`de`) ### Fixed diff --git a/apps/client/src/locales/messages.de.xlf b/apps/client/src/locales/messages.de.xlf index 3e12ef852..9211617f0 100644 --- a/apps/client/src/locales/messages.de.xlf +++ b/apps/client/src/locales/messages.de.xlf @@ -5409,7 +5409,7 @@ , - entnehmen, +  entnehmen, apps/client/src/app/pages/portfolio/fire/fire-page.html 93 From d2fe16c794edc91334b8c58ecb492705b59ef694 Mon Sep 17 00:00:00 2001 From: Tanbir Ali <124070023+tanbirali@users.noreply.github.com> Date: Sat, 11 Oct 2025 23:44:25 +0530 Subject: [PATCH 05/37] Task/refactor transactionCount to activitiesCount in portfolio holding response (#5709) * Refactor transactionCount to activitiesCount in portfolio holding response * Update changelog --- CHANGELOG.md | 1 + apps/api/src/app/portfolio/portfolio.service.ts | 6 +++--- .../holding-detail-dialog.component.ts | 8 +++----- .../holding-detail-dialog/holding-detail-dialog.html | 6 +++--- .../responses/portfolio-holding-response.interface.ts | 2 +- 5 files changed, 11 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1128a6205..d178ae97a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Changed the _As seen in_ section on the landing page to an animated carousel +- Refactored `transactionCount` to `activitiesCount` in the endpoint `GET api/v1/portfolio/holding/:dataSource/:symbol` - Refactored various components to use self-closing tags - Improved the language localization for German (`de`) diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts index e73f79784..f774378ef 100644 --- a/apps/api/src/app/portfolio/portfolio.service.ts +++ b/apps/api/src/app/portfolio/portfolio.service.ts @@ -778,6 +778,7 @@ export class PortfolioService { if (activities.length === 0) { return { activities: [], + activitiesCount: 0, averagePrice: undefined, dataProviderInfo: undefined, dividendInBaseCurrency: undefined, @@ -802,7 +803,6 @@ export class PortfolioService { quantity: undefined, SymbolProfile: undefined, tags: [], - transactionCount: undefined, value: undefined }; } @@ -966,8 +966,8 @@ export class PortfolioService { marketPriceMin, SymbolProfile, tags, - transactionCount, activities: activitiesOfHolding, + activitiesCount: transactionCount, averagePrice: averagePrice.toNumber(), dataProviderInfo: portfolioCalculator.getDataProviderInfos()?.[0], dividendInBaseCurrency: dividendInBaseCurrency.toNumber(), @@ -1070,6 +1070,7 @@ export class PortfolioService { marketPriceMin, SymbolProfile, activities: [], + activitiesCount: 0, averagePrice: 0, dataProviderInfo: undefined, dividendInBaseCurrency: 0, @@ -1095,7 +1096,6 @@ export class PortfolioService { }, quantity: 0, tags: [], - transactionCount: undefined, value: 0 }; } 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 8f6616174..d4c1c59c1 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 @@ -100,6 +100,7 @@ import { HoldingDetailDialogParams } from './interfaces/interfaces'; templateUrl: 'holding-detail-dialog.html' }) export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit { + public activitiesCount: number; public activityForm: FormGroup; public accounts: Account[]; public assetClass: string; @@ -151,8 +152,6 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit { public SymbolProfile: EnhancedSymbolProfile; public tags: Tag[]; public tagsAvailable: Tag[]; - public totalItems: number; - public transactionCount: number; public user: User; public value: number; @@ -261,6 +260,7 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit { .pipe(takeUntil(this.unsubscribeSubject)) .subscribe( ({ + activitiesCount, averagePrice, dataProviderInfo, dividendInBaseCurrency, @@ -279,9 +279,9 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit { quantity, SymbolProfile, tags, - transactionCount, value }) => { + this.activitiesCount = activitiesCount; this.averagePrice = averagePrice; if ( @@ -429,8 +429,6 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit { this.activityForm.setValue({ tags: this.tags }, { emitEvent: false }); - this.transactionCount = transactionCount; - this.totalItems = transactionCount; this.value = value; if (SymbolProfile?.assetClass) { diff --git a/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html index 651f01a65..298692303 100644 --- a/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html +++ b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -223,9 +223,9 @@ - @if (transactionCount === 1) { + @if (activitiesCount === 1) { Activity } @else { Activities @@ -363,7 +363,7 @@ [sortColumn]="sortColumn" [sortDirection]="sortDirection" [sortDisabled]="true" - [totalItems]="totalItems" + [totalItems]="activitiesCount" (activityToClone)="onCloneActivity($event)" (activityToUpdate)="onUpdateActivity($event)" (export)="onExport()" 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 000460228..b82a8f85d 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 @@ -10,6 +10,7 @@ import { Tag } from '@prisma/client'; export interface PortfolioHoldingResponse { activities: Activity[]; + activitiesCount: number; averagePrice: number; dataProviderInfo: DataProviderInfo; dividendInBaseCurrency: number; @@ -34,6 +35,5 @@ export interface PortfolioHoldingResponse { quantity: number; SymbolProfile: EnhancedSymbolProfile; tags: Tag[]; - transactionCount: number; value: number; } From df16aba5c69d3c53557e801b19616600f8cdb2b1 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 11 Oct 2025 20:37:37 +0200 Subject: [PATCH 06/37] Feature/upgrade prisma to version 6.17.1 (#5732) * Upgrade prisma to version 6.17.1 * Update changelog --- CHANGELOG.md | 1 + package-lock.json | 72 +++++++++++++++++++++++------------------------ package.json | 4 +-- 3 files changed, 39 insertions(+), 38 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d178ae97a..e26093a0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Refactored `transactionCount` to `activitiesCount` in the endpoint `GET api/v1/portfolio/holding/:dataSource/:symbol` - Refactored various components to use self-closing tags - Improved the language localization for German (`de`) +- Upgraded `prisma` from version `6.16.1` to `6.16.3` ### Fixed diff --git a/package-lock.json b/package-lock.json index 55e453cce..0f313a101 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,7 +44,7 @@ "@nestjs/schedule": "6.0.0", "@nestjs/serve-static": "5.0.3", "@openrouter/ai-sdk-provider": "0.7.2", - "@prisma/client": "6.16.3", + "@prisma/client": "6.17.1", "@simplewebauthn/browser": "13.1.0", "@simplewebauthn/server": "13.1.1", "@stripe/stripe-js": "7.9.0", @@ -149,7 +149,7 @@ "nx": "21.5.1", "prettier": "3.6.2", "prettier-plugin-organize-attributes": "1.0.0", - "prisma": "6.16.3", + "prisma": "6.17.1", "react": "18.2.0", "react-dom": "18.2.0", "replace-in-file": "8.3.0", @@ -11960,9 +11960,9 @@ "license": "MIT" }, "node_modules/@prisma/client": { - "version": "6.16.3", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.16.3.tgz", - "integrity": "sha512-JfNfAtXG+/lIopsvoZlZiH2k5yNx87mcTS4t9/S5oufM1nKdXYxOvpDC1XoTCFBa5cQh7uXnbMPsmZrwZY80xw==", + "version": "6.17.1", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.17.1.tgz", + "integrity": "sha512-zL58jbLzYamjnNnmNA51IOZdbk5ci03KviXCuB0Tydc9btH2kDWsi1pQm2VecviRTM7jGia0OPPkgpGnT3nKvw==", "hasInstallScript": true, "license": "Apache-2.0", "engines": { @@ -11982,9 +11982,9 @@ } }, "node_modules/@prisma/config": { - "version": "6.16.3", - "resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.16.3.tgz", - "integrity": "sha512-VlsLnG4oOuKGGMToEeVaRhoTBZu5H3q51jTQXb/diRags3WV0+BQK5MolJTtP6G7COlzoXmWeS11rNBtvg+qFQ==", + "version": "6.17.1", + "resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.17.1.tgz", + "integrity": "sha512-fs8wY6DsvOCzuiyWVckrVs1LOcbY4LZNz8ki4uUIQ28jCCzojTGqdLhN2Jl5lDnC1yI8/gNIKpsWDM8pLhOdwA==", "devOptional": true, "license": "Apache-2.0", "dependencies": { @@ -11995,53 +11995,53 @@ } }, "node_modules/@prisma/debug": { - "version": "6.16.3", - "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.16.3.tgz", - "integrity": "sha512-89DdqWtdKd7qoc9/qJCKLTazj3W3zPEiz0hc7HfZdpjzm21c7orOUB5oHWJsG+4KbV4cWU5pefq3CuDVYF9vgA==", + "version": "6.17.1", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.17.1.tgz", + "integrity": "sha512-Vf7Tt5Wh9XcndpbmeotuqOMLWPTjEKCsgojxXP2oxE1/xYe7PtnP76hsouG9vis6fctX+TxgmwxTuYi/+xc7dQ==", "devOptional": true, "license": "Apache-2.0" }, "node_modules/@prisma/engines": { - "version": "6.16.3", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.16.3.tgz", - "integrity": "sha512-b+Rl4nzQDcoqe6RIpSHv8f5lLnwdDGvXhHjGDiokObguAAv/O1KaX1Oc69mBW/GFWKQpCkOraobLjU6s1h8HGg==", + "version": "6.17.1", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.17.1.tgz", + "integrity": "sha512-D95Ik3GYZkqZ8lSR4EyFOJ/tR33FcYRP8kK61o+WMsyD10UfJwd7+YielflHfKwiGodcqKqoraWw8ElAgMDbPw==", "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "6.16.3", - "@prisma/engines-version": "6.16.1-1.bb420e667c1820a8c05a38023385f6cc7ef8e83a", - "@prisma/fetch-engine": "6.16.3", - "@prisma/get-platform": "6.16.3" + "@prisma/debug": "6.17.1", + "@prisma/engines-version": "6.17.1-1.272a37d34178c2894197e17273bf937f25acdeac", + "@prisma/fetch-engine": "6.17.1", + "@prisma/get-platform": "6.17.1" } }, "node_modules/@prisma/engines-version": { - "version": "6.16.1-1.bb420e667c1820a8c05a38023385f6cc7ef8e83a", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.16.1-1.bb420e667c1820a8c05a38023385f6cc7ef8e83a.tgz", - "integrity": "sha512-fftRmosBex48Ph1v2ll1FrPpirwtPZpNkE5CDCY1Lw2SD2ctyrLlVlHiuxDAAlALwWBOkPbAll4+EaqdGuMhJw==", + "version": "6.17.1-1.272a37d34178c2894197e17273bf937f25acdeac", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.17.1-1.272a37d34178c2894197e17273bf937f25acdeac.tgz", + "integrity": "sha512-17140E3huOuD9lMdJ9+SF/juOf3WR3sTJMVyyenzqUPbuH+89nPhSWcrY+Mf7tmSs6HvaO+7S+HkELinn6bhdg==", "devOptional": true, "license": "Apache-2.0" }, "node_modules/@prisma/fetch-engine": { - "version": "6.16.3", - "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.16.3.tgz", - "integrity": "sha512-bUoRIkVaI+CCaVGrSfcKev0/Mk4ateubqWqGZvQ9uCqFv2ENwWIR3OeNuGin96nZn5+SkebcD7RGgKr/+mJelw==", + "version": "6.17.1", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.17.1.tgz", + "integrity": "sha512-AYZiHOs184qkDMiTeshyJCtyL4yERkjfTkJiSJdYuSfc24m94lTNL5+GFinZ6vVz+ktX4NJzHKn1zIFzGTWrWg==", "devOptional": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "6.16.3", - "@prisma/engines-version": "6.16.1-1.bb420e667c1820a8c05a38023385f6cc7ef8e83a", - "@prisma/get-platform": "6.16.3" + "@prisma/debug": "6.17.1", + "@prisma/engines-version": "6.17.1-1.272a37d34178c2894197e17273bf937f25acdeac", + "@prisma/get-platform": "6.17.1" } }, "node_modules/@prisma/get-platform": { - "version": "6.16.3", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.16.3.tgz", - "integrity": "sha512-X1LxiFXinJ4iQehrodGp0f66Dv6cDL0GbRlcCoLtSu6f4Wi+hgo7eND/afIs5029GQLgNWKZ46vn8hjyXTsHLA==", + "version": "6.17.1", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.17.1.tgz", + "integrity": "sha512-AKEn6fsfz0r482S5KRDFlIGEaq9wLNcgalD1adL+fPcFFblIKs1sD81kY/utrHdqKuVC6E1XSRpegDK3ZLL4Qg==", "devOptional": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "6.16.3" + "@prisma/debug": "6.17.1" } }, "node_modules/@redis/client": { @@ -35747,15 +35747,15 @@ } }, "node_modules/prisma": { - "version": "6.16.3", - "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.16.3.tgz", - "integrity": "sha512-4tJq3KB9WRshH5+QmzOLV54YMkNlKOtLKaSdvraI5kC/axF47HuOw6zDM8xrxJ6s9o2WodY654On4XKkrobQdQ==", + "version": "6.17.1", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.17.1.tgz", + "integrity": "sha512-ac6h0sM1Tg3zu8NInY+qhP/S9KhENVaw9n1BrGKQVFu05JT5yT5Qqqmb8tMRIE3ZXvVj4xcRA5yfrsy4X7Yy5g==", "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@prisma/config": "6.16.3", - "@prisma/engines": "6.16.3" + "@prisma/config": "6.17.1", + "@prisma/engines": "6.17.1" }, "bin": { "prisma": "build/index.js" diff --git a/package.json b/package.json index 5f99a4d11..b0a801bf8 100644 --- a/package.json +++ b/package.json @@ -90,7 +90,7 @@ "@nestjs/schedule": "6.0.0", "@nestjs/serve-static": "5.0.3", "@openrouter/ai-sdk-provider": "0.7.2", - "@prisma/client": "6.16.3", + "@prisma/client": "6.17.1", "@simplewebauthn/browser": "13.1.0", "@simplewebauthn/server": "13.1.1", "@stripe/stripe-js": "7.9.0", @@ -195,7 +195,7 @@ "nx": "21.5.1", "prettier": "3.6.2", "prettier-plugin-organize-attributes": "1.0.0", - "prisma": "6.16.3", + "prisma": "6.17.1", "react": "18.2.0", "react-dom": "18.2.0", "replace-in-file": "8.3.0", From a9f38aaf90451079b7db39e31db661932a6514ce Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 11 Oct 2025 20:38:09 +0200 Subject: [PATCH 07/37] Task/remove deprecated position endpoints from portfolio controller (#5733) * Remove deprecated endpoints * GET api/v1/portfolio/position/:dataSource/:symbol * PUT api/v1/portfolio/position/:dataSource/:symbol/tags * Update changelog --- CHANGELOG.md | 2 + .../src/app/portfolio/portfolio.controller.ts | 66 ------------------- 2 files changed, 2 insertions(+), 66 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e26093a0c..01883f0f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Changed the _As seen in_ section on the landing page to an animated carousel - Refactored `transactionCount` to `activitiesCount` in the endpoint `GET api/v1/portfolio/holding/:dataSource/:symbol` - Refactored various components to use self-closing tags +- Removed the deprecated endpoint `GET api/v1/portfolio/position/:dataSource/:symbol` +- Removed the deprecated endpoint `PUT api/v1/portfolio/position/:dataSource/:symbol/tags` - Improved the language localization for German (`de`) - Upgraded `prisma` from version `6.16.1` to `6.16.3` diff --git a/apps/api/src/app/portfolio/portfolio.controller.ts b/apps/api/src/app/portfolio/portfolio.controller.ts index 5659818a8..19b0636c7 100644 --- a/apps/api/src/app/portfolio/portfolio.controller.ts +++ b/apps/api/src/app/portfolio/portfolio.controller.ts @@ -610,36 +610,6 @@ export class PortfolioController { return performanceInformation; } - /** - * @deprecated - */ - @Get('position/:dataSource/:symbol') - @UseInterceptors(RedactValuesInResponseInterceptor) - @UseInterceptors(TransformDataSourceInRequestInterceptor) - @UseInterceptors(TransformDataSourceInResponseInterceptor) - @UseGuards(AuthGuard('jwt'), HasPermissionGuard) - public async getPosition( - @Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string, - @Param('dataSource') dataSource: DataSource, - @Param('symbol') symbol: string - ): Promise { - const holding = await this.portfolioService.getHolding({ - dataSource, - impersonationId, - symbol, - userId: this.request.user.id - }); - - if (!holding) { - throw new HttpException( - getReasonPhrase(StatusCodes.NOT_FOUND), - StatusCodes.NOT_FOUND - ); - } - - return holding; - } - @Get('report') @UseGuards(AuthGuard('jwt'), HasPermissionGuard) public async getReport( @@ -699,40 +669,4 @@ export class PortfolioController { userId: this.request.user.id }); } - - /** - * @deprecated - */ - @HasPermission(permissions.updateOrder) - @Put('position/:dataSource/:symbol/tags') - @UseInterceptors(TransformDataSourceInRequestInterceptor) - @UseGuards(AuthGuard('jwt'), HasPermissionGuard) - public async updatePositionTags( - @Body() data: UpdateHoldingTagsDto, - @Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string, - @Param('dataSource') dataSource: DataSource, - @Param('symbol') symbol: string - ): Promise { - const holding = await this.portfolioService.getHolding({ - dataSource, - impersonationId, - symbol, - userId: this.request.user.id - }); - - if (!holding) { - throw new HttpException( - getReasonPhrase(StatusCodes.NOT_FOUND), - StatusCodes.NOT_FOUND - ); - } - - await this.portfolioService.updateTags({ - dataSource, - impersonationId, - symbol, - tags: data.tags, - userId: this.request.user.id - }); - } } From fc4d5774fa586f9eff75f2e1db3d6b67a4f05347 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Szymon=20=C5=81=C4=85giewka?= Date: Sat, 11 Oct 2025 20:42:40 +0200 Subject: [PATCH 08/37] =?UTF-8?q?Bugfix/enable=20IPv6=20connectivity=C2=A0?= =?UTF-8?q?for=20Redis=20in=20job=20queue=20module=20(#5726)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Enable IPv6 connectivity for Redis in job queue module * Update changelog --- CHANGELOG.md | 1 + apps/api/src/app/app.module.ts | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 01883f0f3..6139cbbab 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 - Fixed the server startup message to properly display IPv6 addresses +- Enabled IPv6 connectivity for _Redis_ in the job queue module by setting the address family - Fixed an issue where importing custom asset profiles failed due to validation errors ## 2.207.0 - 2025-10-08 diff --git a/apps/api/src/app/app.module.ts b/apps/api/src/app/app.module.ts index 8596aa0eb..86ceede28 100644 --- a/apps/api/src/app/app.module.ts +++ b/apps/api/src/app/app.module.ts @@ -71,9 +71,10 @@ import { UserModule } from './user/user.module'; BullModule.forRoot({ redis: { db: parseInt(process.env.REDIS_DB ?? '0', 10), + family: 0, host: process.env.REDIS_HOST, - port: parseInt(process.env.REDIS_PORT ?? '6379', 10), - password: process.env.REDIS_PASSWORD + password: process.env.REDIS_PASSWORD, + port: parseInt(process.env.REDIS_PORT ?? '6379', 10) } }), CacheModule, From c1c7d82c836b4bc345055bba4090de7527d6c072 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 11 Oct 2025 20:44:59 +0200 Subject: [PATCH 09/37] Release 2.208.0 (#5734) --- CHANGELOG.md | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6139cbbab..ce142e8e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Unreleased +## 2.208.0 - 2025-10-11 ### Added diff --git a/package-lock.json b/package-lock.json index 0f313a101..75e1f50fe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ghostfolio", - "version": "2.207.0", + "version": "2.208.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ghostfolio", - "version": "2.207.0", + "version": "2.208.0", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/package.json b/package.json index b0a801bf8..28968c2c0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ghostfolio", - "version": "2.207.0", + "version": "2.208.0", "homepage": "https://ghostfol.io", "license": "AGPL-3.0", "repository": "https://github.com/ghostfolio/ghostfolio", From 20da95239ec211f3f834ee5014b5a3fd735f2dfb Mon Sep 17 00:00:00 2001 From: Lagmator22 <133108827+Lagmator22@users.noreply.github.com> Date: Sun, 12 Oct 2025 02:18:18 +0530 Subject: [PATCH 10/37] Task/refactor liabilities to liabilitiesInBaseCurrency in portfolio summary interface (#5725) * Refactor liabilities to liabilitiesInBaseCurrency --- apps/api/src/app/portfolio/portfolio.service.ts | 2 +- .../portfolio-summary/portfolio-summary.component.html | 7 +++++-- .../src/lib/interfaces/portfolio-summary.interface.ts | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts index f774378ef..a5bc10fbd 100644 --- a/apps/api/src/app/portfolio/portfolio.service.ts +++ b/apps/api/src/app/portfolio/portfolio.service.ts @@ -2106,7 +2106,7 @@ export class PortfolioService { .plus(fees) .toNumber(), interest: interest.toNumber(), - liabilities: liabilities.toNumber(), + liabilitiesInBaseCurrency: liabilities.toNumber(), totalInvestment: totalInvestment.toNumber(), totalValueInBaseCurrency: netWorth }; 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 19f125523..c8d710019 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 @@ -242,7 +242,10 @@
Liabilities
- @if (summary?.liabilities || summary?.liabilities === 0) { + @if ( + summary?.liabilitiesInBaseCurrency || + summary?.liabilitiesInBaseCurrency === 0 + ) { - }
diff --git a/libs/common/src/lib/interfaces/portfolio-summary.interface.ts b/libs/common/src/lib/interfaces/portfolio-summary.interface.ts index 05fac0ba0..092a4bb97 100644 --- a/libs/common/src/lib/interfaces/portfolio-summary.interface.ts +++ b/libs/common/src/lib/interfaces/portfolio-summary.interface.ts @@ -21,7 +21,7 @@ export interface PortfolioSummary extends PortfolioPerformance { grossPerformance: number; grossPerformanceWithCurrencyEffect: number; interest: number; - liabilities: number; + liabilitiesInBaseCurrency: number; totalBuy: number; totalSell: number; totalValueInBaseCurrency?: number; From 02ef4dc8d9daad84044f3d6b80e0adc468eeb111 Mon Sep 17 00:00:00 2001 From: steffenrapp <88974099+steffenrapp@users.noreply.github.com> Date: Sun, 12 Oct 2025 08:41:32 +0200 Subject: [PATCH 11/37] Task/disable zoom in PWA (#5735) * Disable zoom in PWA * Update changelog --- CHANGELOG.md | 6 ++++++ apps/client/src/index.html | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce142e8e8..c36963d66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Changed + +- Disabled zoom in PWA + ## 2.208.0 - 2025-10-11 ### Added diff --git a/apps/client/src/index.html b/apps/client/src/index.html index cfdcbc4c8..c923e3e0c 100644 --- a/apps/client/src/index.html +++ b/apps/client/src/index.html @@ -13,7 +13,7 @@ From 02ca2eca284d5572848063cde976ef057a8062e8 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sun, 12 Oct 2025 08:56:13 +0200 Subject: [PATCH 12/37] Task/improve changelog entry (#5738) * Update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c36963d66..28818fdfe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -- Disabled zoom in PWA +- Disabled the zoom functionality in the _Progressive Web App_ (PWA) ## 2.208.0 - 2025-10-11 From 5c7d34821e465017ff5e77a361230b47bb0c7eb1 Mon Sep 17 00:00:00 2001 From: David Requeno <108202767+DavidReque@users.noreply.github.com> Date: Sun, 12 Oct 2025 01:30:00 -0600 Subject: [PATCH 13/37] Feature/set up Storybook story for holdings table component (#5697) * Set up Storybook story for holdings table component * Update changelog --- CHANGELOG.md | 4 + .../holdings-table.component.stories.ts | 62 ++++ libs/ui/src/lib/mocks/holdings.ts | 293 +++++++++++++++ .../treemap-chart.component.stories.ts | 351 +----------------- 4 files changed, 361 insertions(+), 349 deletions(-) create mode 100644 libs/ui/src/lib/holdings-table/holdings-table.component.stories.ts create mode 100644 libs/ui/src/lib/mocks/holdings.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 28818fdfe..529d6f83a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Added + +- Added a _Storybook_ story for the holdings table component + ### Changed - Disabled the zoom functionality in the _Progressive Web App_ (PWA) diff --git a/libs/ui/src/lib/holdings-table/holdings-table.component.stories.ts b/libs/ui/src/lib/holdings-table/holdings-table.component.stories.ts new file mode 100644 index 000000000..37e73e9e4 --- /dev/null +++ b/libs/ui/src/lib/holdings-table/holdings-table.component.stories.ts @@ -0,0 +1,62 @@ +import { CommonModule } from '@angular/common'; +import { MatButtonModule } from '@angular/material/button'; +import { MatDialogModule } from '@angular/material/dialog'; +import { MatPaginatorModule } from '@angular/material/paginator'; +import { MatSortModule } from '@angular/material/sort'; +import { MatTableModule } from '@angular/material/table'; +import { moduleMetadata } from '@storybook/angular'; +import type { Meta, StoryObj } from '@storybook/angular'; +import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; + +import { GfEntityLogoComponent } from '../entity-logo'; +import { holdings } from '../mocks/holdings'; +import { GfValueComponent } from '../value'; +import { GfHoldingsTableComponent } from './holdings-table.component'; + +export default { + title: 'Holdings Table', + component: GfHoldingsTableComponent, + decorators: [ + moduleMetadata({ + imports: [ + CommonModule, + GfEntityLogoComponent, + GfValueComponent, + MatButtonModule, + MatDialogModule, + MatPaginatorModule, + MatSortModule, + MatTableModule, + NgxSkeletonLoaderModule + ] + }) + ] +} as Meta; + +type Story = StoryObj; + +export const Loading: Story = { + args: { + holdings: undefined, + baseCurrency: 'USD', + deviceType: 'desktop', + hasPermissionToOpenDetails: false, + hasPermissionToShowQuantities: true, + hasPermissionToShowValues: true, + locale: 'en-US', + pageSize: Number.MAX_SAFE_INTEGER + } +}; + +export const Default: Story = { + args: { + holdings, + baseCurrency: 'USD', + deviceType: 'desktop', + hasPermissionToOpenDetails: false, + hasPermissionToShowQuantities: true, + hasPermissionToShowValues: true, + locale: 'en-US', + pageSize: Number.MAX_SAFE_INTEGER + } +}; diff --git a/libs/ui/src/lib/mocks/holdings.ts b/libs/ui/src/lib/mocks/holdings.ts new file mode 100644 index 000000000..6e8485c82 --- /dev/null +++ b/libs/ui/src/lib/mocks/holdings.ts @@ -0,0 +1,293 @@ +import { PortfolioPosition } from '@ghostfolio/common/interfaces'; + +export const holdings: PortfolioPosition[] = [ + { + allocationInPercentage: 0.042990776363386086, + assetClass: 'EQUITY' as any, + assetClassLabel: 'Equity', + assetSubClass: 'STOCK' as any, + assetSubClassLabel: 'Stock', + countries: [ + { + code: 'US', + weight: 1, + continent: 'North America', + name: 'United States' + } + ], + currency: 'USD', + dataSource: 'YAHOO' as any, + dateOfFirstActivity: new Date('2021-12-01T00:00:00.000Z'), + dividend: 0, + grossPerformance: 3856, + grossPerformancePercent: 0.46047289228564603, + grossPerformancePercentWithCurrencyEffect: 0.46047289228564603, + grossPerformanceWithCurrencyEffect: 3856, + holdings: [], + investment: 8374, + marketPrice: 244.6, + name: 'Apple Inc', + netPerformance: 3855, + netPerformancePercent: 0.460353475041796, + netPerformancePercentWithCurrencyEffect: 0.036440677966101696, + netPerformanceWithCurrencyEffect: 430, + quantity: 50, + sectors: [ + { + name: 'Technology', + weight: 1 + } + ], + symbol: 'AAPL', + tags: [], + transactionCount: 1, + url: 'https://www.apple.com', + valueInBaseCurrency: 12230 + }, + { + allocationInPercentage: 0.02377401948293552, + assetClass: 'EQUITY' as any, + assetClassLabel: 'Equity', + assetSubClass: 'STOCK' as any, + assetSubClassLabel: 'Stock', + countries: [ + { + code: 'DE', + weight: 1, + continent: 'Europe', + name: 'Germany' + } + ], + currency: 'EUR', + dataSource: 'YAHOO' as any, + dateOfFirstActivity: new Date('2021-04-23T00:00:00.000Z'), + dividend: 192, + grossPerformance: 2226.700251889169, + grossPerformancePercent: 0.49083842309827874, + grossPerformancePercentWithCurrencyEffect: 0.29306136948826367, + grossPerformanceWithCurrencyEffect: 1532.8272791336772, + holdings: [], + investment: 4536.523929471033, + marketPrice: 322.2, + name: 'Allianz SE', + netPerformance: 2222.2921914357685, + netPerformancePercent: 0.48986674069961134, + netPerformancePercentWithCurrencyEffect: 0.034489367670592026, + netPerformanceWithCurrencyEffect: 225.48257403052068, + quantity: 20, + sectors: [ + { + name: 'Financial Services', + weight: 1 + } + ], + symbol: 'ALV.DE', + tags: [], + transactionCount: 2, + url: 'https://www.allianz.com', + valueInBaseCurrency: 6763.224181360202 + }, + { + allocationInPercentage: 0.08038536990007467, + assetClass: 'EQUITY' as any, + assetClassLabel: 'Equity', + assetSubClass: 'STOCK' as any, + assetSubClassLabel: 'Stock', + countries: [ + { + code: 'US', + weight: 1, + continent: 'North America', + name: 'United States' + } + ], + currency: 'USD', + dataSource: 'YAHOO' as any, + dateOfFirstActivity: new Date('2018-10-01T00:00:00.000Z'), + dividend: 0, + grossPerformance: 12758.05, + grossPerformancePercent: 1.2619300787837724, + grossPerformancePercentWithCurrencyEffect: 1.2619300787837724, + grossPerformanceWithCurrencyEffect: 12758.05, + holdings: [], + investment: 10109.95, + marketPrice: 228.68, + name: 'Amazon.com, Inc.', + netPerformance: 12677.26, + netPerformancePercent: 1.253938941339967, + netPerformancePercentWithCurrencyEffect: -0.037866008722316276, + netPerformanceWithCurrencyEffect: -899.99926757812, + quantity: 100, + sectors: [ + { + name: 'Consumer Discretionary', + weight: 1 + } + ], + symbol: 'AMZN', + tags: [], + transactionCount: 1, + url: 'https://www.aboutamazon.com', + valueInBaseCurrency: 22868 + }, + { + allocationInPercentage: 0.19216416482928922, + assetClass: 'LIQUIDITY' as any, + assetClassLabel: 'Liquidity', + assetSubClass: 'CRYPTOCURRENCY' as any, + assetSubClassLabel: 'Cryptocurrency', + countries: [], + currency: 'USD', + dataSource: 'COINGECKO' as any, + dateOfFirstActivity: new Date('2017-08-16T00:00:00.000Z'), + dividend: 0, + grossPerformance: 52666.7898248, + grossPerformancePercent: 26.333394912400003, + grossPerformancePercentWithCurrencyEffect: 26.333394912400003, + grossPerformanceWithCurrencyEffect: 52666.7898248, + holdings: [], + investment: 1999.9999999999998, + marketPrice: 97364, + name: 'Bitcoin', + netPerformance: 52636.8898248, + netPerformancePercent: 26.3184449124, + netPerformancePercentWithCurrencyEffect: -0.04760906442310894, + netPerformanceWithCurrencyEffect: -2732.737808972287, + quantity: 0.5614682, + sectors: [], + symbol: 'bitcoin', + tags: [], + transactionCount: 1, + url: null, + valueInBaseCurrency: 54666.7898248 + }, + { + allocationInPercentage: 0.04307127421937313, + assetClass: 'EQUITY' as any, + assetClassLabel: 'Equity', + assetSubClass: 'STOCK' as any, + assetSubClassLabel: 'Stock', + countries: [ + { + code: 'US', + weight: 1, + continent: 'North America', + name: 'United States' + } + ], + currency: 'USD', + dataSource: 'YAHOO' as any, + dateOfFirstActivity: new Date('2023-01-03T00:00:00.000Z'), + dividend: 0, + grossPerformance: 5065.5, + grossPerformancePercent: 0.7047750229568411, + grossPerformancePercentWithCurrencyEffect: 0.7047750229568411, + grossPerformanceWithCurrencyEffect: 5065.5, + holdings: [], + investment: 7187.4, + marketPrice: 408.43, + name: 'Microsoft Corporation', + netPerformance: 5065.5, + netPerformancePercent: 0.7047750229568411, + netPerformancePercentWithCurrencyEffect: -0.015973588391056275, + netPerformanceWithCurrencyEffect: -198.899926757814, + quantity: 30, + sectors: [ + { + name: 'Technology', + weight: 1 + } + ], + symbol: 'MSFT', + tags: [], + transactionCount: 1, + url: 'https://www.microsoft.com', + valueInBaseCurrency: 12252.9 + }, + { + allocationInPercentage: 0.18762679306394897, + assetClass: 'EQUITY' as any, + assetClassLabel: 'Equity', + assetSubClass: 'STOCK' as any, + assetSubClassLabel: 'Stock', + countries: [ + { + code: 'US', + weight: 1, + continent: 'North America', + name: 'United States' + } + ], + currency: 'USD', + dataSource: 'YAHOO' as any, + dateOfFirstActivity: new Date('2017-01-03T00:00:00.000Z'), + dividend: 0, + grossPerformance: 51227.500000005, + grossPerformancePercent: 23.843379101756675, + grossPerformancePercentWithCurrencyEffect: 23.843379101756675, + grossPerformanceWithCurrencyEffect: 51227.500000005, + holdings: [], + investment: 2148.499999995, + marketPrice: 355.84, + name: 'Tesla, Inc.', + netPerformance: 51197.500000005, + netPerformancePercent: 23.829415871596066, + netPerformancePercentWithCurrencyEffect: -0.12051410125545206, + netPerformanceWithCurrencyEffect: -7314.00091552734, + quantity: 150, + sectors: [ + { + name: 'Consumer Discretionary', + weight: 1 + } + ], + symbol: 'TSLA', + tags: [], + transactionCount: 1, + url: 'https://www.tesla.com', + valueInBaseCurrency: 53376 + }, + { + allocationInPercentage: 0.053051250766657634, + assetClass: 'EQUITY' as any, + assetClassLabel: 'Equity', + assetSubClass: 'ETF' as any, + assetSubClassLabel: 'ETF', + countries: [ + { + code: 'US', + weight: 1, + continent: 'North America', + name: 'United States' + } + ], + currency: 'USD', + dataSource: 'YAHOO' as any, + dateOfFirstActivity: new Date('2019-03-01T00:00:00.000Z'), + dividend: 0, + grossPerformance: 6845.8, + grossPerformancePercent: 1.0164758094605268, + grossPerformancePercentWithCurrencyEffect: 1.0164758094605268, + grossPerformanceWithCurrencyEffect: 6845.8, + holdings: [], + investment: 8246.2, + marketPrice: 301.84, + name: 'Vanguard Total Stock Market Index Fund ETF Shares', + netPerformance: 6746.3, + netPerformancePercent: 1.0017018833976383, + netPerformancePercentWithCurrencyEffect: 0.01085061564051406, + netPerformanceWithCurrencyEffect: 161.99969482422, + quantity: 50, + sectors: [ + { + name: 'Equity', + weight: 1 + } + ], + symbol: 'VTI', + tags: [], + transactionCount: 5, + url: 'https://www.vanguard.com', + valueInBaseCurrency: 15092 + } +]; 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 2000b4f98..c8951ce6b 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 @@ -4,6 +4,7 @@ import { moduleMetadata } from '@storybook/angular'; import type { Meta, StoryObj } from '@storybook/angular'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; +import { holdings } from '../mocks/holdings'; import { GfTreemapChartComponent } from './treemap-chart.component'; export default { @@ -34,359 +35,11 @@ type Story = StoryObj; export const Default: Story = { args: { + holdings, baseCurrency: 'USD', colorScheme: 'LIGHT', cursor: undefined, dateRange: 'mtd', - holdings: [ - { - allocationInPercentage: 0.042990776363386086, - assetClass: 'EQUITY', - assetSubClass: 'STOCK', - countries: [], - currency: 'USD', - dataSource: 'YAHOO', - dateOfFirstActivity: new Date('2021-12-01T00:00:00.000Z'), - dividend: 0, - grossPerformance: 3856, - grossPerformancePercent: 0.46047289228564603, - grossPerformancePercentWithCurrencyEffect: 0.46047289228564603, - grossPerformanceWithCurrencyEffect: 3856, - holdings: [], - investment: 8374, - marketPrice: 244.6, - name: 'Apple Inc', - netPerformance: 3855, - netPerformancePercent: 0.460353475041796, - netPerformancePercentWithCurrencyEffect: 0.036440677966101696, - netPerformanceWithCurrencyEffect: 430, - quantity: 50, - sectors: [], - symbol: 'AAPL', - tags: [], - transactionCount: 1, - url: 'https://www.apple.com', - valueInBaseCurrency: 12230 - }, - { - allocationInPercentage: 0.02377401948293552, - assetClass: 'EQUITY', - assetSubClass: 'STOCK', - countries: [], - currency: 'EUR', - dataSource: 'YAHOO', - dateOfFirstActivity: new Date('2021-04-23T00:00:00.000Z'), - dividend: 192, - grossPerformance: 2226.700251889169, - grossPerformancePercent: 0.49083842309827874, - grossPerformancePercentWithCurrencyEffect: 0.29306136948826367, - grossPerformanceWithCurrencyEffect: 1532.8272791336772, - holdings: [], - investment: 4536.523929471033, - marketPrice: 322.2, - name: 'Allianz SE', - netPerformance: 2222.2921914357685, - netPerformancePercent: 0.48986674069961134, - netPerformancePercentWithCurrencyEffect: 0.034489367670592026, - netPerformanceWithCurrencyEffect: 225.48257403052068, - quantity: 20, - sectors: [], - symbol: 'ALV.DE', - tags: [], - transactionCount: 2, - url: 'https://www.allianz.com', - valueInBaseCurrency: 6763.224181360202 - }, - { - allocationInPercentage: 0.08038536990007467, - assetClass: 'EQUITY', - assetSubClass: 'STOCK', - countries: [], - currency: 'USD', - dataSource: 'YAHOO', - dateOfFirstActivity: new Date('2018-10-01T00:00:00.000Z'), - dividend: 0, - grossPerformance: 12758.05, - grossPerformancePercent: 1.2619300787837724, - grossPerformancePercentWithCurrencyEffect: 1.2619300787837724, - grossPerformanceWithCurrencyEffect: 12758.05, - holdings: [], - investment: 10109.95, - marketPrice: 228.68, - name: 'Amazon.com, Inc.', - netPerformance: 12677.26, - netPerformancePercent: 1.253938941339967, - netPerformancePercentWithCurrencyEffect: -0.037866008722316276, - netPerformanceWithCurrencyEffect: -899.99926757812, - quantity: 100, - sectors: [], - symbol: 'AMZN', - tags: [], - transactionCount: 1, - url: 'https://www.aboutamazon.com', - valueInBaseCurrency: 22868 - }, - { - allocationInPercentage: 0.19216416482928922, - assetClass: 'LIQUIDITY', - assetSubClass: 'CRYPTOCURRENCY', - countries: [], - currency: 'USD', - dataSource: 'COINGECKO', - dateOfFirstActivity: new Date('2017-08-16T00:00:00.000Z'), - dividend: 0, - grossPerformance: 52666.7898248, - grossPerformancePercent: 26.333394912400003, - grossPerformancePercentWithCurrencyEffect: 26.333394912400003, - grossPerformanceWithCurrencyEffect: 52666.7898248, - holdings: [], - investment: 1999.9999999999998, - marketPrice: 97364, - name: 'Bitcoin', - netPerformance: 52636.8898248, - netPerformancePercent: 26.3184449124, - netPerformancePercentWithCurrencyEffect: -0.04760906442310894, - netPerformanceWithCurrencyEffect: -2732.737808972287, - quantity: 0.5614682, - sectors: [], - symbol: 'bitcoin', - tags: [], - transactionCount: 1, - url: null, - valueInBaseCurrency: 54666.7898248 - }, - { - allocationInPercentage: 0.007378652850073097, - assetClass: 'FIXED_INCOME', - assetSubClass: 'BOND', - countries: [], - currency: 'EUR', - dataSource: 'MANUAL', - dateOfFirstActivity: new Date('2021-02-01T00:00:00.000Z'), - dividend: 11.45, - grossPerformance: 0, - grossPerformancePercent: 0, - grossPerformancePercentWithCurrencyEffect: -0.1247202380342517, - grossPerformanceWithCurrencyEffect: -258.2576430160448, - holdings: [], - investment: 2099.0764063811926, - marketPrice: 1, - name: 'Bondora Go & Grow', - netPerformance: 0, - netPerformancePercent: 0, - netPerformancePercentWithCurrencyEffect: 0.009445843828715519, - netPerformanceWithCurrencyEffect: 19.6420125363184, - quantity: 2000, - sectors: [], - symbol: 'BONDORA_GO_AND_GROW', - tags: [], - transactionCount: 5, - url: null, - valueInBaseCurrency: 2099.0764063811926 - }, - { - allocationInPercentage: 0.07787531695543741, - assetClass: 'EQUITY', - assetSubClass: 'ETF', - countries: [], - currency: 'CHF', - dataSource: 'MANUAL', - dateOfFirstActivity: new Date('2021-04-01T00:00:00.000Z'), - dividend: 0, - grossPerformance: 4550.843985045582, - grossPerformancePercent: 0.3631417324494093, - grossPerformancePercentWithCurrencyEffect: 0.42037247857285137, - grossPerformanceWithCurrencyEffect: 5107.057936556927, - holdings: [], - investment: 17603.097090932337, - marketPrice: 188.22, - name: 'frankly Extreme 95 Index', - netPerformance: 4550.843985045582, - netPerformancePercent: 0.3631417324494093, - netPerformancePercentWithCurrencyEffect: 0.026190604904358043, - netPerformanceWithCurrencyEffect: 565.4165171873152, - quantity: 105.87328656807, - sectors: [], - symbol: 'FRANKLY95P', - tags: [], - transactionCount: 6, - url: 'https://www.frankly.ch', - valueInBaseCurrency: 22153.941075977917 - }, - { - allocationInPercentage: 0.04307127421937313, - assetClass: 'EQUITY', - assetSubClass: 'STOCK', - countries: [], - currency: 'USD', - dataSource: 'YAHOO', - dateOfFirstActivity: new Date('2023-01-03T00:00:00.000Z'), - dividend: 0, - grossPerformance: 5065.5, - grossPerformancePercent: 0.7047750229568411, - grossPerformancePercentWithCurrencyEffect: 0.7047750229568411, - grossPerformanceWithCurrencyEffect: 5065.5, - holdings: [], - investment: 7187.4, - marketPrice: 408.43, - name: 'Microsoft Corporation', - netPerformance: 5065.5, - netPerformancePercent: 0.7047750229568411, - netPerformancePercentWithCurrencyEffect: -0.015973588391056275, - netPerformanceWithCurrencyEffect: -198.899926757814, - quantity: 30, - sectors: [], - symbol: 'MSFT', - tags: [], - transactionCount: 1, - url: 'https://www.microsoft.com', - valueInBaseCurrency: 12252.9 - }, - { - allocationInPercentage: 0.18762679306394897, - assetClass: 'EQUITY', - assetSubClass: 'STOCK', - countries: [], - currency: 'USD', - dataSource: 'YAHOO', - dateOfFirstActivity: new Date('2017-01-03T00:00:00.000Z'), - dividend: 0, - grossPerformance: 51227.500000005, - grossPerformancePercent: 23.843379101756675, - grossPerformancePercentWithCurrencyEffect: 23.843379101756675, - grossPerformanceWithCurrencyEffect: 51227.500000005, - holdings: [], - investment: 2148.499999995, - marketPrice: 355.84, - name: 'Tesla, Inc.', - netPerformance: 51197.500000005, - netPerformancePercent: 23.829415871596066, - netPerformancePercentWithCurrencyEffect: -0.12051410125545206, - netPerformanceWithCurrencyEffect: -7314.00091552734, - quantity: 150, - sectors: [], - symbol: 'TSLA', - tags: [], - transactionCount: 1, - url: 'https://www.tesla.com', - valueInBaseCurrency: 53376 - }, - { - allocationInPercentage: 0.053051250766657634, - assetClass: 'EQUITY', - assetSubClass: 'ETF', - countries: [], - currency: 'USD', - dataSource: 'YAHOO', - dateOfFirstActivity: new Date('2019-03-01T00:00:00.000Z'), - dividend: 0, - grossPerformance: 6845.8, - grossPerformancePercent: 1.0164758094605268, - grossPerformancePercentWithCurrencyEffect: 1.0164758094605268, - grossPerformanceWithCurrencyEffect: 6845.8, - holdings: [], - investment: 8246.2, - marketPrice: 301.84, - name: 'Vanguard Total Stock Market Index Fund ETF Shares', - netPerformance: 6746.3, - netPerformancePercent: 1.0017018833976383, - netPerformancePercentWithCurrencyEffect: 0.01085061564051406, - netPerformanceWithCurrencyEffect: 161.99969482422, - quantity: 50, - sectors: [], - symbol: 'VTI', - tags: [], - transactionCount: 5, - url: 'https://www.vanguard.com', - valueInBaseCurrency: 15092 - }, - { - allocationInPercentage: 0.0836576192450555, - assetClass: 'EQUITY', - assetSubClass: 'ETF', - countries: [], - currency: 'CHF', - dataSource: 'YAHOO', - dateOfFirstActivity: new Date('2018-03-01T00:00:00.000Z'), - dividend: 0, - grossPerformance: 6462.42356864925, - grossPerformancePercent: 0.5463044783973836, - grossPerformancePercentWithCurrencyEffect: 0.6282343505275325, - grossPerformanceWithCurrencyEffect: 7121.935580698947, - holdings: [], - investment: 17336.464702612564, - marketPrice: 129.74, - name: 'Vanguard FTSE All-World UCITS ETF', - netPerformance: 6373.040578098944, - netPerformancePercent: 0.5387484388540966, - netPerformancePercentWithCurrencyEffect: 0.008409682389650015, - netPerformanceWithCurrencyEffect: 198.47200506226807, - quantity: 165, - sectors: [], - symbol: 'VWRL.SW', - tags: [], - transactionCount: 5, - url: 'https://www.vanguard.com', - valueInBaseCurrency: 23798.888271261814 - }, - { - allocationInPercentage: 0.03265192235898284, - assetClass: 'EQUITY', - assetSubClass: 'ETF', - countries: [], - currency: 'EUR', - dataSource: 'YAHOO', - dateOfFirstActivity: new Date('2021-08-19T00:00:00.000Z'), - dividend: 0, - grossPerformance: 3112.7991183879094, - grossPerformancePercent: 0.5040147846036197, - grossPerformancePercentWithCurrencyEffect: 0.3516875105542396, - grossPerformanceWithCurrencyEffect: 2416.799201046856, - holdings: [], - investment: 6176.007556675063, - marketPrice: 118.005, - name: 'Xtrackers MSCI World UCITS ETF 1C', - netPerformance: 3081.4179261125105, - netPerformancePercent: 0.4989336392216841, - netPerformancePercentWithCurrencyEffect: 0.006460676966633529, - netPerformanceWithCurrencyEffect: 59.626750161726044, - quantity: 75, - sectors: [], - symbol: 'XDWD.DE', - tags: [], - transactionCount: 1, - url: null, - valueInBaseCurrency: 9288.806675062973 - }, - { - allocationInPercentage: 0.17537283996478595, - assetClass: 'LIQUIDITY', - assetSubClass: 'CASH', - countries: [], - currency: 'USD', - dataSource: 'MANUAL', - dateOfFirstActivity: new Date('2021-04-01T00:00:00.000Z'), - dividend: 0, - grossPerformance: 0, - grossPerformancePercent: 0, - grossPerformancePercentWithCurrencyEffect: 0, - grossPerformanceWithCurrencyEffect: 0, - holdings: [], - investment: 49890, - marketPrice: 0, - name: 'USD', - netPerformance: 0, - netPerformancePercent: 0, - netPerformancePercentWithCurrencyEffect: 0, - netPerformanceWithCurrencyEffect: 0, - quantity: 0, - sectors: [], - symbol: 'USD', - tags: [], - transactionCount: 0, - valueInBaseCurrency: 49890 - } - ], locale: 'en-US' } }; From 74a4d830c83916fc7fdf6501038f62e8d615f6b1 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sun, 12 Oct 2025 09:30:26 +0200 Subject: [PATCH 14/37] Feature/improve accounts table Storybook story (#5739) * Improve Storybook story --- .../src/lib/accounts-table/accounts-table.component.stories.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libs/ui/src/lib/accounts-table/accounts-table.component.stories.ts b/libs/ui/src/lib/accounts-table/accounts-table.component.stories.ts index 42f48fffd..aeda82fd9 100644 --- a/libs/ui/src/lib/accounts-table/accounts-table.component.stories.ts +++ b/libs/ui/src/lib/accounts-table/accounts-table.component.stories.ts @@ -112,6 +112,7 @@ export const Loading: Story = { accounts: undefined, baseCurrency: 'USD', deviceType: 'desktop', + hasPermissionToOpenDetails: false, locale: 'en-US', showActions: false, showAllocationInPercentage: false, @@ -128,6 +129,7 @@ export const Default: Story = { accounts, baseCurrency: 'USD', deviceType: 'desktop', + hasPermissionToOpenDetails: false, locale: 'en-US', showActions: false, showAllocationInPercentage: false, @@ -147,6 +149,7 @@ export const WithoutFooter: Story = { accounts, baseCurrency: 'USD', deviceType: 'desktop', + hasPermissionToOpenDetails: false, locale: 'en-US', showActions: false, showAllocationInPercentage: false, From 8d6153fa52707d5e22a2d4d7687e88fcea1e1be2 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sun, 12 Oct 2025 13:48:23 +0200 Subject: [PATCH 15/37] Feature/use asset profile resolutions in getQuotes() of FMP service (#5743) * Use asset profile resolutions in getQuotes() * Update changelog --- CHANGELOG.md | 1 + .../financial-modeling-prep.service.ts | 60 ++++++++++++------- 2 files changed, 40 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 529d6f83a..cee2662a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Disabled the zoom functionality in the _Progressive Web App_ (PWA) +- Optimized the get quotes functionality by utilizing the asset profile resolutions in the _Financial Modeling Prep_ service ## 2.208.0 - 2025-10-11 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 8bb8f8327..3b0d8ba72 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 @@ -12,6 +12,7 @@ import { IDataProviderHistoricalResponse, IDataProviderResponse } from '@ghostfolio/api/services/interfaces/interfaces'; +import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service'; import { DEFAULT_CURRENCY, REPLACE_NAME_PARTS @@ -49,7 +50,8 @@ export class FinancialModelingPrepService implements DataProviderInterface { public constructor( private readonly configurationService: ConfigurationService, - private readonly cryptocurrencyService: CryptocurrencyService + private readonly cryptocurrencyService: CryptocurrencyService, + private readonly prismaService: PrismaService ) { this.apiKey = this.configurationService.get( 'API_KEY_FINANCIAL_MODELING_PREP' @@ -220,7 +222,7 @@ export class FinancialModelingPrepService implements DataProviderInterface { public getDataProviderInfo(): DataProviderInfo { return { - dataSource: DataSource.FINANCIAL_MODELING_PREP, + dataSource: this.getName(), isPremium: true, name: 'Financial Modeling Prep', url: 'https://financialmodelingprep.com/developer/docs' @@ -359,25 +361,41 @@ export class FinancialModelingPrepService implements DataProviderInterface { [symbol: string]: Pick; } = {}; - const quotes = await fetch( - `${this.getUrl({ version: 'stable' })}/batch-quote-short?symbols=${symbols.join(',')}&apikey=${this.apiKey}`, - { - signal: AbortSignal.timeout(requestTimeout) - } - ).then((res) => res.json()); + const [assetProfileResolutions, quotes] = await Promise.all([ + this.prismaService.assetProfileResolution.findMany({ + where: { + dataSourceTarget: this.getDataProviderInfo().dataSource, + symbolTarget: { in: symbols } + } + }), + fetch( + `${this.getUrl({ version: 'stable' })}/batch-quote-short?symbols=${symbols.join(',')}&apikey=${this.apiKey}`, + { + signal: AbortSignal.timeout(requestTimeout) + } + ).then((res) => res.json()) + ]); - await Promise.all( - quotes.map(({ symbol }) => { - return this.getAssetProfile({ - requestTimeout, - symbol - }).then((assetProfile) => { - if (assetProfile?.currency) { - currencyBySymbolMap[symbol] = { currency: assetProfile.currency }; - } - }); - }) - ); + if (assetProfileResolutions.length === symbols.length) { + for (const { currency, symbolTarget } of assetProfileResolutions) { + currencyBySymbolMap[symbolTarget] = { currency }; + } + } else { + await Promise.all( + quotes.map(({ symbol }) => { + return this.getAssetProfile({ + requestTimeout, + symbol + }).then((assetProfile) => { + if (assetProfile?.currency) { + currencyBySymbolMap[symbol] = { + currency: assetProfile.currency + }; + } + }); + }) + ); + } for (const { price, symbol } of quotes) { let marketState: MarketState = 'delayed'; @@ -394,7 +412,7 @@ export class FinancialModelingPrepService implements DataProviderInterface { marketState, currency: currencyBySymbolMap[symbol]?.currency, dataProviderInfo: this.getDataProviderInfo(), - dataSource: DataSource.FINANCIAL_MODELING_PREP, + dataSource: this.getDataProviderInfo().dataSource, marketPrice: price }; } From e8366603ad34d3ae707ebb63dbe6f5a624be80a1 Mon Sep 17 00:00:00 2001 From: Aditya Pawar <143347456+JustAdi10@users.noreply.github.com> Date: Sun, 12 Oct 2025 17:19:28 +0530 Subject: [PATCH 16/37] Task/extract footer to component (#5702) * Extract footer to component * Update changelog --- CHANGELOG.md | 1 + apps/client/src/app/app.component.html | 188 +----------------- apps/client/src/app/app.component.scss | 18 -- apps/client/src/app/app.component.ts | 35 ---- apps/client/src/app/app.module.ts | 7 +- .../components/footer/footer.component.html | 181 +++++++++++++++++ .../components/footer/footer.component.scss | 16 ++ .../app/components/footer/footer.component.ts | 74 +++++++ 8 files changed, 278 insertions(+), 242 deletions(-) create mode 100644 apps/client/src/app/components/footer/footer.component.html create mode 100644 apps/client/src/app/components/footer/footer.component.scss create mode 100644 apps/client/src/app/components/footer/footer.component.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index cee2662a2..9a555fa34 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 - Disabled the zoom functionality in the _Progressive Web App_ (PWA) - Optimized the get quotes functionality by utilizing the asset profile resolutions in the _Financial Modeling Prep_ service +- Extracted the footer to a component ## 2.208.0 - 2025-10-11 diff --git a/apps/client/src/app/app.component.html b/apps/client/src/app/app.component.html index 2f5faa24c..61d5023e2 100644 --- a/apps/client/src/app/app.component.html +++ b/apps/client/src/app/app.component.html @@ -11,8 +11,8 @@ > You are using the Live Demo. Create Account - + + } @if (!canCreateAccount && user?.systemMessage) {
@if (showFooter) { -
-
-
-
- -
-
-
Personal Finance
-
    - @if (hasPermissionToAccessFearAndGreedIndex) { -
  • - Markets -
  • - } -
  • Resources
  • -
-
-
-
Ghostfolio
- -
-
-
Community
- -
-
-
-
- © 2021 - {{ currentYear }} - Ghostfolio -
-
-
-
- The risk of loss in trading can be substantial. It is not advisable - to invest money you may need in the short term. -
-
-
-
-
-
Ghostfolio
-
-
+
+
} diff --git a/apps/client/src/app/app.component.scss b/apps/client/src/app/app.component.scss index f848fe6ff..57fedacf4 100644 --- a/apps/client/src/app/app.component.scss +++ b/apps/client/src/app/app.component.scss @@ -34,18 +34,6 @@ } } - footer { - background-color: rgba(var(--palette-foreground-text), 0.05); - font-size: 90%; - - .logotype { - font-size: 13vw; - letter-spacing: -0.03em; - margin-bottom: -5svw; - opacity: 0.05; - } - } - header { height: var(--mat-toolbar-standard-height); } @@ -54,9 +42,3 @@ min-height: calc(100svh - var(--mat-toolbar-standard-height)); } } - -:host-context(.theme-dark) { - footer { - background-color: rgba(var(--palette-foreground-text-dark), 0.05); - } -} diff --git a/apps/client/src/app/app.component.ts b/apps/client/src/app/app.component.ts index e7d0960e2..bddd7d3da 100644 --- a/apps/client/src/app/app.component.ts +++ b/apps/client/src/app/app.component.ts @@ -52,36 +52,16 @@ export class AppComponent implements OnDestroy, OnInit { public canCreateAccount: boolean; public currentRoute: string; public currentSubRoute: string; - public currentYear = new Date().getFullYear(); public deviceType: string; public hasImpersonationId: boolean; public hasInfoMessage: boolean; - public hasPermissionForStatistics: boolean; - public hasPermissionForSubscription: boolean; - public hasPermissionToAccessFearAndGreedIndex: boolean; public hasPermissionToChangeDateRange: boolean; public hasPermissionToChangeFilters: boolean; public hasPromotion = false; public hasTabs = false; public info: InfoItem; public pageTitle: string; - public routerLinkAbout = publicRoutes.about.routerLink; - public routerLinkAboutChangelog = - publicRoutes.about.subRoutes.changelog.routerLink; - public routerLinkAboutLicense = - publicRoutes.about.subRoutes.license.routerLink; - public routerLinkAboutPrivacyPolicy = - publicRoutes.about.subRoutes.privacyPolicy.routerLink; - public routerLinkAboutTermsOfService = - publicRoutes.about.subRoutes.termsOfService.routerLink; - public routerLinkBlog = publicRoutes.blog.routerLink; - public routerLinkFaq = publicRoutes.faq.routerLink; - public routerLinkFeatures = publicRoutes.features.routerLink; - public routerLinkMarkets = publicRoutes.markets.routerLink; - public routerLinkOpenStartup = publicRoutes.openStartup.routerLink; - public routerLinkPricing = publicRoutes.pricing.routerLink; public routerLinkRegister = publicRoutes.register.routerLink; - public routerLinkResources = publicRoutes.resources.routerLink; public showFooter = false; public user: User; @@ -126,21 +106,6 @@ export class AppComponent implements OnDestroy, OnInit { this.deviceType = this.deviceService.getDeviceInfo().deviceType; this.info = this.dataService.fetchInfo(); - this.hasPermissionForSubscription = hasPermission( - this.info?.globalPermissions, - permissions.enableSubscription - ); - - this.hasPermissionForStatistics = hasPermission( - this.info?.globalPermissions, - permissions.enableStatistics - ); - - this.hasPermissionToAccessFearAndGreedIndex = hasPermission( - this.info?.globalPermissions, - permissions.enableFearAndGreedIndex - ); - this.hasPromotion = !!this.info?.subscriptionOffer?.coupon || !!this.info?.subscriptionOffer?.durationExtension; diff --git a/apps/client/src/app/app.module.ts b/apps/client/src/app/app.module.ts index de52a043d..63de8fca7 100644 --- a/apps/client/src/app/app.module.ts +++ b/apps/client/src/app/app.module.ts @@ -1,5 +1,3 @@ -import { GfLogoComponent } from '@ghostfolio/ui/logo'; - import { Platform } from '@angular/cdk/platform'; import { provideHttpClient, @@ -20,7 +18,6 @@ import { BrowserModule } from '@angular/platform-browser'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { ServiceWorkerModule } from '@angular/service-worker'; import { provideIonicAngular } from '@ionic/angular/standalone'; -import { IonIcon } from '@ionic/angular/standalone'; import { provideMarkdown } from 'ngx-markdown'; import { provideNgxSkeletonLoader } from 'ngx-skeleton-loader'; import { NgxStripeModule, STRIPE_PUBLISHABLE_KEY } from 'ngx-stripe'; @@ -30,6 +27,7 @@ import { CustomDateAdapter } from './adapter/custom-date-adapter'; import { DateFormats } from './adapter/date-formats'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; +import { GfFooterComponent } from './components/footer/footer.component'; import { GfHeaderComponent } from './components/header/header.component'; import { authInterceptorProviders } from './core/auth.interceptor'; import { httpResponseInterceptorProviders } from './core/http-response.interceptor'; @@ -47,10 +45,9 @@ export function NgxStripeFactory(): string { AppRoutingModule, BrowserAnimationsModule, BrowserModule, + GfFooterComponent, GfHeaderComponent, - GfLogoComponent, GfNotificationModule, - IonIcon, MatAutocompleteModule, MatChipsModule, MatNativeDateModule, diff --git a/apps/client/src/app/components/footer/footer.component.html b/apps/client/src/app/components/footer/footer.component.html new file mode 100644 index 000000000..155f27f68 --- /dev/null +++ b/apps/client/src/app/components/footer/footer.component.html @@ -0,0 +1,181 @@ +
+
+
+ +
+
+
Personal Finance
+
    + @if (hasPermissionToAccessFearAndGreedIndex) { +
  • + Markets +
  • + } +
  • Resources
  • +
+
+
+
Ghostfolio
+ +
+
+
Community
+ +
+
+
+
+ © 2021 - {{ currentYear }} + Ghostfolio +
+
+
+
+ The risk of loss in trading can be substantial. It is not advisable to + invest money you may need in the short term. +
+
+
+
+
+
Ghostfolio
+
+
diff --git a/apps/client/src/app/components/footer/footer.component.scss b/apps/client/src/app/components/footer/footer.component.scss new file mode 100644 index 000000000..3bff80ccd --- /dev/null +++ b/apps/client/src/app/components/footer/footer.component.scss @@ -0,0 +1,16 @@ +:host { + background-color: rgba(var(--palette-foreground-text), 0.05); + display: block; + font-size: 90%; + + .logotype { + font-size: 13vw; + letter-spacing: -0.03em; + margin-bottom: -5svw; + opacity: 0.05; + } +} + +:host-context(.theme-dark) { + background-color: rgba(var(--palette-foreground-text-dark), 0.05); +} diff --git a/apps/client/src/app/components/footer/footer.component.ts b/apps/client/src/app/components/footer/footer.component.ts new file mode 100644 index 000000000..a5820832e --- /dev/null +++ b/apps/client/src/app/components/footer/footer.component.ts @@ -0,0 +1,74 @@ +import { InfoItem, User } from '@ghostfolio/common/interfaces'; +import { hasPermission, permissions } from '@ghostfolio/common/permissions'; +import { publicRoutes } from '@ghostfolio/common/routes/routes'; +import { GfLogoComponent } from '@ghostfolio/ui/logo'; + +import { CommonModule } from '@angular/common'; +import { + ChangeDetectionStrategy, + Component, + CUSTOM_ELEMENTS_SCHEMA, + Input, + OnChanges +} from '@angular/core'; +import { RouterModule } from '@angular/router'; +import { IonIcon } from '@ionic/angular/standalone'; +import { addIcons } from 'ionicons'; +import { openOutline } from 'ionicons/icons'; + +@Component({ + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [CommonModule, GfLogoComponent, IonIcon, RouterModule], + schemas: [CUSTOM_ELEMENTS_SCHEMA], + selector: 'gf-footer', + styleUrls: ['./footer.component.scss'], + templateUrl: './footer.component.html' +}) +export class GfFooterComponent implements OnChanges { + @Input() public info: InfoItem; + @Input() public user: User; + + public currentYear = new Date().getFullYear(); + public hasPermissionForStatistics: boolean; + public hasPermissionForSubscription: boolean; + public hasPermissionToAccessFearAndGreedIndex: boolean; + public routerLinkAbout = publicRoutes.about.routerLink; + public routerLinkAboutChangelog = + publicRoutes.about.subRoutes.changelog.routerLink; + public routerLinkAboutLicense = + publicRoutes.about.subRoutes.license.routerLink; + public routerLinkAboutPrivacyPolicy = + publicRoutes.about.subRoutes.privacyPolicy.routerLink; + public routerLinkAboutTermsOfService = + publicRoutes.about.subRoutes.termsOfService.routerLink; + public routerLinkBlog = publicRoutes.blog.routerLink; + public routerLinkFaq = publicRoutes.faq.routerLink; + public routerLinkFeatures = publicRoutes.features.routerLink; + public routerLinkMarkets = publicRoutes.markets.routerLink; + public routerLinkOpenStartup = publicRoutes.openStartup.routerLink; + public routerLinkPricing = publicRoutes.pricing.routerLink; + public routerLinkResources = publicRoutes.resources.routerLink; + + public constructor() { + addIcons({ + openOutline + }); + } + + public ngOnChanges() { + this.hasPermissionForStatistics = hasPermission( + this.info?.globalPermissions, + permissions.enableStatistics + ); + + this.hasPermissionForSubscription = hasPermission( + this.info?.globalPermissions, + permissions.enableSubscription + ); + + this.hasPermissionToAccessFearAndGreedIndex = hasPermission( + this.info?.globalPermissions, + permissions.enableFearAndGreedIndex + ); + } +} From 2ff02a0a9ce4ef23b27eaf33cd9681c808d259f9 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Mon, 13 Oct 2025 17:40:28 +0200 Subject: [PATCH 17/37] Feature/improve currency validation in search functionality of data provider service (#5745) * Improve currency validation * Update changelog --- CHANGELOG.md | 1 + .../services/data-provider/data-provider.service.ts | 7 +++++-- libs/common/src/lib/helper.ts | 10 +++++++--- package-lock.json | 7 ------- package.json | 1 - 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a555fa34..38981a8c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Disabled the zoom functionality in the _Progressive Web App_ (PWA) +- Improved the currency validation in the search functionality of the data provider service - Optimized the get quotes functionality by utilizing the asset profile resolutions in the _Financial Modeling Prep_ service - Extracted the footer to a component 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 8754c3537..c70d643db 100644 --- a/apps/api/src/services/data-provider/data-provider.service.ts +++ b/apps/api/src/services/data-provider/data-provider.service.ts @@ -645,8 +645,11 @@ export class DataProviderService implements OnModuleInit { const filteredItems = lookupItems .filter(({ currency }) => { - // Only allow symbols with supported currency - return currency ? true : false; + if (includeIndices) { + return true; + } + + return currency ? isCurrency(currency) : false; }) .map((lookupItem) => { if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) { diff --git a/libs/common/src/lib/helper.ts b/libs/common/src/lib/helper.ts index e5dc187ff..97b762267 100644 --- a/libs/common/src/lib/helper.ts +++ b/libs/common/src/lib/helper.ts @@ -1,7 +1,7 @@ -import * as currencies from '@dinero.js/currencies'; import { NumberParser } from '@internationalized/number'; import { Type as ActivityType, DataSource, MarketData } from '@prisma/client'; import { Big } from 'big.js'; +import { isISO4217CurrencyCode } from 'class-validator'; import { getDate, getMonth, @@ -340,8 +340,12 @@ export function interpolate(template: string, context: any) { }); } -export function isCurrency(aCurrency = '') { - return currencies[aCurrency] || isDerivedCurrency(aCurrency); +export function isCurrency(aCurrency: string) { + if (!aCurrency) { + return false; + } + + return isISO4217CurrencyCode(aCurrency) || isDerivedCurrency(aCurrency); } export function isDerivedCurrency(aCurrency: string) { diff --git a/package-lock.json b/package-lock.json index 75e1f50fe..e65c23ac7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,7 +28,6 @@ "@dfinity/candid": "0.15.7", "@dfinity/identity": "0.15.7", "@dfinity/principal": "0.15.7", - "@dinero.js/currencies": "2.0.0-alpha.8", "@internationalized/number": "3.6.3", "@ionic/angular": "8.7.3", "@keyv/redis": "4.4.0", @@ -5018,12 +5017,6 @@ "ts-node": "^10.8.2" } }, - "node_modules/@dinero.js/currencies": { - "version": "2.0.0-alpha.8", - "resolved": "https://registry.npmjs.org/@dinero.js/currencies/-/currencies-2.0.0-alpha.8.tgz", - "integrity": "sha512-zApiqtuuPwjiM9LJA5/kNcT48VSHRiz2/mktkXjIpfxrJKzthXybUAgEenExIH6dYhLDgVmsLQZtZFOsdYl0Ag==", - "license": "MIT" - }, "node_modules/@discoveryjs/json-ext": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.6.3.tgz", diff --git a/package.json b/package.json index 28968c2c0..cb0ba6731 100644 --- a/package.json +++ b/package.json @@ -74,7 +74,6 @@ "@dfinity/candid": "0.15.7", "@dfinity/identity": "0.15.7", "@dfinity/principal": "0.15.7", - "@dinero.js/currencies": "2.0.0-alpha.8", "@internationalized/number": "3.6.3", "@ionic/angular": "8.7.3", "@keyv/redis": "4.4.0", From 948df81a0d32f1f9d48b18a5bddf4906c2d89a25 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Mon, 13 Oct 2025 17:41:53 +0200 Subject: [PATCH 18/37] Feature/use asset profile resolutions in getQuotes() of FMP service (part 2) (#5750) * Use asset profile resolutions in getQuotes() --- .../financial-modeling-prep.service.ts | 42 +++++++++++++------ 1 file changed, 29 insertions(+), 13 deletions(-) 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 3b0d8ba72..7c1395a23 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 @@ -373,26 +373,42 @@ export class FinancialModelingPrepService implements DataProviderInterface { { signal: AbortSignal.timeout(requestTimeout) } - ).then((res) => res.json()) + ).then( + (res) => res.json() as unknown as { price: number; symbol: string }[] + ) ]); - if (assetProfileResolutions.length === symbols.length) { - for (const { currency, symbolTarget } of assetProfileResolutions) { - currencyBySymbolMap[symbolTarget] = { currency }; + for (const { currency, symbolTarget } of assetProfileResolutions) { + currencyBySymbolMap[symbolTarget] = { currency }; + } + + const resolvedSymbols = assetProfileResolutions.map( + ({ symbolTarget }) => { + return symbolTarget; } - } else { + ); + + const symbolsToFetch = quotes + .map(({ symbol }) => { + return symbol; + }) + .filter((symbol) => { + return !resolvedSymbols.includes(symbol); + }); + + if (symbolsToFetch.length > 0) { await Promise.all( - quotes.map(({ symbol }) => { - return this.getAssetProfile({ + symbolsToFetch.map(async (symbol) => { + const assetProfile = await this.getAssetProfile({ requestTimeout, symbol - }).then((assetProfile) => { - if (assetProfile?.currency) { - currencyBySymbolMap[symbol] = { - currency: assetProfile.currency - }; - } }); + + if (assetProfile?.currency) { + currencyBySymbolMap[symbol] = { + currency: assetProfile.currency + }; + } }) ); } From 058d7caacdbe5eb2c91d2a5794f31ab5901835a2 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Mon, 13 Oct 2025 17:43:21 +0200 Subject: [PATCH 19/37] Bugfix/respect includeIndices flag in search functionality of FMP service (#5746) * Respect includeIndices in search() * Update changelog --- CHANGELOG.md | 4 +++ .../financial-modeling-prep.service.ts | 31 ++++++++++++------- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 38981a8c6..bb4e798ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Optimized the get quotes functionality by utilizing the asset profile resolutions in the _Financial Modeling Prep_ service - Extracted the footer to a component +### Fixed + +- Respected the include indices flag in the search functionality of the _Financial Modeling Prep_ service + ## 2.208.0 - 2025-10-11 ### Added 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 7c1395a23..2e59acb01 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 @@ -454,6 +454,7 @@ export class FinancialModelingPrepService implements DataProviderInterface { } public async search({ + includeIndices = false, query, requestTimeout = this.configurationService.get('REQUEST_TIMEOUT') }: GetSearchParams): Promise { @@ -500,17 +501,25 @@ export class FinancialModelingPrepService implements DataProviderInterface { } ).then((res) => res.json()); - items = result.map(({ currency, name, symbol }) => { - return { - currency, - symbol, - assetClass: undefined, // TODO - assetSubClass: undefined, // TODO - dataProviderInfo: this.getDataProviderInfo(), - dataSource: this.getName(), - name: this.formatName({ name }) - }; - }); + items = result + .filter(({ symbol }) => { + if (includeIndices === false && symbol.startsWith('^')) { + return false; + } + + return true; + }) + .map(({ currency, name, symbol }) => { + return { + currency, + symbol, + assetClass: undefined, // TODO + assetSubClass: undefined, // TODO + dataProviderInfo: this.getDataProviderInfo(), + dataSource: this.getName(), + name: this.formatName({ name }) + }; + }); } } catch (error) { let message = error; From 3034745e7d87495b70c494e0a936fbedd54f219f Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Mon, 13 Oct 2025 17:46:32 +0200 Subject: [PATCH 20/37] Feature/improve currency validation in getAssetProfiles() functionality of data provider service (#5747) * Improve currency validation * Update changelog --- CHANGELOG.md | 1 + .../ghostfolio/ghostfolio.controller.ts | 10 +++++++++- .../ghostfolio/ghostfolio.service.ts | 20 +++++++++---------- .../data-provider/data-provider.service.ts | 14 ++++++++++--- .../errors/asset-profile-invalid.error.ts | 7 +++++++ .../financial-modeling-prep.service.ts | 4 ++++ 6 files changed, 42 insertions(+), 14 deletions(-) create mode 100644 apps/api/src/services/data-provider/errors/asset-profile-invalid.error.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index bb4e798ca..31d691c87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Disabled the zoom functionality in the _Progressive Web App_ (PWA) +- Improved the currency validation in the get asset profiles functionality of the data provider service - Improved the currency validation in the search functionality of the data provider service - Optimized the get quotes functionality by utilizing the asset profile resolutions in the _Financial Modeling Prep_ service - Extracted the footer to a component diff --git a/apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.controller.ts b/apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.controller.ts index 7cb2520bb..04165e9a1 100644 --- a/apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.controller.ts +++ b/apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.controller.ts @@ -1,5 +1,6 @@ import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator'; import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard'; +import { AssetProfileInvalidError } from '@ghostfolio/api/services/data-provider/errors/asset-profile-invalid.error'; import { parseDate } from '@ghostfolio/common/helper'; import { DataProviderGhostfolioAssetProfileResponse, @@ -66,7 +67,14 @@ export class GhostfolioController { }); return assetProfile; - } catch { + } catch (error) { + if (error instanceof AssetProfileInvalidError) { + throw new HttpException( + getReasonPhrase(StatusCodes.NOT_FOUND), + StatusCodes.NOT_FOUND + ); + } + throw new HttpException( getReasonPhrase(StatusCodes.INTERNAL_SERVER_ERROR), StatusCodes.INTERNAL_SERVER_ERROR 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 cc92efa02..ac5881c4d 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,10 +40,7 @@ export class GhostfolioService { private readonly propertyService: PropertyService ) {} - public async getAssetProfile({ - requestTimeout = this.configurationService.get('REQUEST_TIMEOUT'), - symbol - }: GetAssetProfileParams) { + public async getAssetProfile({ symbol }: GetAssetProfileParams) { let result: DataProviderGhostfolioAssetProfileResponse = {}; try { @@ -51,12 +48,15 @@ export class GhostfolioService { for (const dataProviderService of this.getDataProviderServices()) { promises.push( - dataProviderService - .getAssetProfile({ - requestTimeout, - symbol - }) - .then(async (assetProfile) => { + this.dataProviderService + .getAssetProfiles([ + { + symbol, + dataSource: dataProviderService.getName() + } + ]) + .then(async (assetProfiles) => { + const assetProfile = assetProfiles[symbol]; const dataSourceOrigin = DataSource.GHOSTFOLIO; if (assetProfile) { 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 c70d643db..6d6054287 100644 --- a/apps/api/src/services/data-provider/data-provider.service.ts +++ b/apps/api/src/services/data-provider/data-provider.service.ts @@ -35,6 +35,8 @@ import { eachDayOfInterval, format, isValid } from 'date-fns'; import { groupBy, isEmpty, isNumber, uniqWith } from 'lodash'; import ms from 'ms'; +import { AssetProfileInvalidError } from './errors/asset-profile-invalid.error'; + @Injectable() export class DataProviderService implements OnModuleInit { private dataProviderMapping: { [dataProviderName: string]: string }; @@ -106,9 +108,9 @@ export class DataProviderService implements OnModuleInit { ); promises.push( - promise.then((symbolProfile) => { - if (symbolProfile) { - response[symbol] = symbolProfile; + promise.then((assetProfile) => { + if (isCurrency(assetProfile?.currency)) { + response[symbol] = assetProfile; } }) ); @@ -117,6 +119,12 @@ export class DataProviderService implements OnModuleInit { try { await Promise.all(promises); + + if (isEmpty(response)) { + throw new AssetProfileInvalidError( + 'No valid asset profiles have been found' + ); + } } catch (error) { Logger.error(error, 'DataProviderService'); diff --git a/apps/api/src/services/data-provider/errors/asset-profile-invalid.error.ts b/apps/api/src/services/data-provider/errors/asset-profile-invalid.error.ts new file mode 100644 index 000000000..bfbea6040 --- /dev/null +++ b/apps/api/src/services/data-provider/errors/asset-profile-invalid.error.ts @@ -0,0 +1,7 @@ +export class AssetProfileInvalidError extends Error { + public constructor(message: string) { + super(message); + + this.name = 'AssetProfileInvalidError'; + } +} 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 2e59acb01..689f59fec 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 @@ -102,6 +102,10 @@ export class FinancialModelingPrepService implements DataProviderInterface { } ).then((res) => res.json()); + if (!assetProfile) { + throw new Error(`${symbol} not found`); + } + const { assetClass, assetSubClass } = this.parseAssetClass(assetProfile); From 7dacc10946cc51a048d6db76d4ad32427cc0db96 Mon Sep 17 00:00:00 2001 From: Mariam Saeed <69825646+Mariam-Saeed@users.noreply.github.com> Date: Wed, 15 Oct 2025 21:10:19 +0300 Subject: [PATCH 21/37] Bugfix/reset scroll position on page change (#5753) * Reset scroll position on page change * Update changelog --- CHANGELOG.md | 1 + apps/client/src/app/app-routing.module.ts | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 31d691c87..fff28224a 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 - Respected the include indices flag in the search functionality of the _Financial Modeling Prep_ service +- Fixed an issue where the scroll position was not restored when changing pages ## 2.208.0 - 2025-10-11 diff --git a/apps/client/src/app/app-routing.module.ts b/apps/client/src/app/app-routing.module.ts index 0e5a2dead..0ceee3725 100644 --- a/apps/client/src/app/app-routing.module.ts +++ b/apps/client/src/app/app-routing.module.ts @@ -155,8 +155,9 @@ const routes: Routes = [ // Preload all lazy loaded modules with the attribute preload === true { anchorScrolling: 'enabled', - preloadingStrategy: ModulePreloadService - // enableTracing: true // <-- debugging purposes only + // enableTracing: true, // <-- debugging purposes only + preloadingStrategy: ModulePreloadService, + scrollPositionRestoration: 'top' } ) ], From 33d9ba0063d8e9b95b3df7bf7f26fe65b464ffc9 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Thu, 16 Oct 2025 17:45:43 +0200 Subject: [PATCH 22/37] Feature/add Stealth Wealth to glossary (#5754) * Add Stealth Wealth * Update changelog --- CHANGELOG.md | 1 + .../glossary/resources-glossary.component.html | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fff28224a..5024d0541 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 +- Extended the glossary of the resources page by _Stealth Wealth_ - Added a _Storybook_ story for the holdings table component ### Changed diff --git a/apps/client/src/app/pages/resources/glossary/resources-glossary.component.html b/apps/client/src/app/pages/resources/glossary/resources-glossary.component.html index 123b4dac9..b028734a7 100644 --- a/apps/client/src/app/pages/resources/glossary/resources-glossary.component.html +++ b/apps/client/src/app/pages/resources/glossary/resources-glossary.component.html @@ -132,6 +132,23 @@
+
+
+

Stealth Wealth

+
+ Stealth wealth is a lifestyle choice where you don’t openly show + off your wealth, but instead live quietly to maintain privacy and + security. +
+ +
+
From 3caa3c010efc289d1b13a76349110de34f782f86 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Thu, 16 Oct 2025 17:46:26 +0200 Subject: [PATCH 23/37] Bugfix/dark mode in logo carousel component (#5758) * Fix dark mode * Update changelog --- CHANGELOG.md | 1 + .../logo-carousel.component.scss | 20 +++++++------------ 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5024d0541..adc9dbf53 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 - Respected the include indices flag in the search functionality of the _Financial Modeling Prep_ service - Fixed an issue where the scroll position was not restored when changing pages +- Fixed the dark mode in the _As seen in_ section on the landing page ## 2.208.0 - 2025-10-11 diff --git a/libs/ui/src/lib/logo-carousel/logo-carousel.component.scss b/libs/ui/src/lib/logo-carousel/logo-carousel.component.scss index d8a8865f7..18c3a26cb 100644 --- a/libs/ui/src/lib/logo-carousel/logo-carousel.component.scss +++ b/libs/ui/src/lib/logo-carousel/logo-carousel.component.scss @@ -194,19 +194,13 @@ ); } - .logo { - &.logo-alternative-to, - &.logo-dev-community, - &.logo-hacker-news, - &.logo-openalternative, - &.logo-privacy-tools, - &.logo-reddit, - &.logo-sackgeld, - &.logo-selfh-st, - &.logo-sourceforge, - &.logo-umbrel, - &.logo-unraid { - background-color: rgba(var(--light-primary-text)); + .logo-carousel-track { + .logo-carousel-item { + .logo { + &.mask { + background-color: rgba(var(--light-secondary-text)); + } + } } } } From db2c2426c648ec39e5d9393c3b0e80c63dad506c Mon Sep 17 00:00:00 2001 From: Dibyendu Sahoo Date: Fri, 17 Oct 2025 00:16:24 +0530 Subject: [PATCH 24/37] Task/refactor interest to interestInBaseCurrency in portfolio summary interface (#5763) * Refactor interest to interestInBaseCurrency --- apps/api/src/app/portfolio/portfolio.controller.ts | 2 +- apps/api/src/app/portfolio/portfolio.service.ts | 2 +- apps/api/src/helper/object.helper.spec.ts | 4 ++-- .../portfolio-summary/portfolio-summary.component.html | 2 +- libs/common/src/lib/interfaces/portfolio-summary.interface.ts | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/api/src/app/portfolio/portfolio.controller.ts b/apps/api/src/app/portfolio/portfolio.controller.ts index 19b0636c7..f6f8e3d80 100644 --- a/apps/api/src/app/portfolio/portfolio.controller.ts +++ b/apps/api/src/app/portfolio/portfolio.controller.ts @@ -197,7 +197,7 @@ export class PortfolioController { 'filteredValueInBaseCurrency', 'grossPerformance', 'grossPerformanceWithCurrencyEffect', - 'interest', + 'interestInBaseCurrency', 'items', 'liabilities', 'netPerformance', diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts index a5bc10fbd..bbfb31b79 100644 --- a/apps/api/src/app/portfolio/portfolio.service.ts +++ b/apps/api/src/app/portfolio/portfolio.service.ts @@ -2105,7 +2105,7 @@ export class PortfolioService { ) .plus(fees) .toNumber(), - interest: interest.toNumber(), + interestInBaseCurrency: interest.toNumber(), liabilitiesInBaseCurrency: liabilities.toNumber(), totalInvestment: totalInvestment.toNumber(), totalValueInBaseCurrency: netWorth diff --git a/apps/api/src/helper/object.helper.spec.ts b/apps/api/src/helper/object.helper.spec.ts index d7caf9bc9..433490325 100644 --- a/apps/api/src/helper/object.helper.spec.ts +++ b/apps/api/src/helper/object.helper.spec.ts @@ -1536,7 +1536,7 @@ describe('redactAttributes', () => { fireWealth: null, grossPerformance: null, grossPerformanceWithCurrencyEffect: null, - interest: null, + interestInBaseCurrency: null, items: null, liabilities: null, totalInvestment: null, @@ -3039,7 +3039,7 @@ describe('redactAttributes', () => { fireWealth: null, grossPerformance: null, grossPerformanceWithCurrencyEffect: null, - interest: null, + interestInBaseCurrency: null, items: null, liabilities: null, totalInvestment: null, 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 c8d710019..b20b6b263 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 @@ -302,7 +302,7 @@ [isCurrency]="true" [locale]="locale" [unit]="baseCurrency" - [value]="isLoading ? undefined : summary?.interest" + [value]="isLoading ? undefined : summary?.interestInBaseCurrency" /> diff --git a/libs/common/src/lib/interfaces/portfolio-summary.interface.ts b/libs/common/src/lib/interfaces/portfolio-summary.interface.ts index 092a4bb97..f08eb61b8 100644 --- a/libs/common/src/lib/interfaces/portfolio-summary.interface.ts +++ b/libs/common/src/lib/interfaces/portfolio-summary.interface.ts @@ -20,7 +20,7 @@ export interface PortfolioSummary extends PortfolioPerformance { fireWealth: FireWealth; grossPerformance: number; grossPerformanceWithCurrencyEffect: number; - interest: number; + interestInBaseCurrency: number; liabilitiesInBaseCurrency: number; totalBuy: number; totalSell: number; From 835bde6662c2aa5118fc0dccb38aa972caffb640 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Thu, 16 Oct 2025 20:51:14 +0200 Subject: [PATCH 25/37] Feature/extend pricing page (#5761) * Extend pricing page * Update changelog --- CHANGELOG.md | 1 + .../pages/pricing/pricing-page.component.ts | 10 +++++ .../src/app/pages/pricing/pricing-page.html | 43 +++++++++++++++---- 3 files changed, 46 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index adc9dbf53..0fb55409a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Extended the glossary of the resources page by _Stealth Wealth_ +- Extended the content of the pricing page - Added a _Storybook_ story for the holdings table component ### Changed diff --git a/apps/client/src/app/pages/pricing/pricing-page.component.ts b/apps/client/src/app/pages/pricing/pricing-page.component.ts index 170d70914..8bc3e3a67 100644 --- a/apps/client/src/app/pages/pricing/pricing-page.component.ts +++ b/apps/client/src/app/pages/pricing/pricing-page.component.ts @@ -69,6 +69,16 @@ export class GfPricingPageComponent implements OnDestroy, OnInit { public professionalDataProviderTooltipPremium = translate( 'PROFESSIONAL_DATA_PROVIDER_TOOLTIP_PREMIUM' ); + public referralBrokers = [ + 'DEGIRO', + 'finpension', + 'frankly', + 'Interactive Brokers', + 'Mintos', + 'Swissquote', + 'VIAC', + 'Zak' + ]; public routerLinkFeatures = publicRoutes.features.routerLink; public routerLinkRegister = publicRoutes.register.routerLink; public user: User; diff --git a/apps/client/src/app/pages/pricing/pricing-page.html b/apps/client/src/app/pages/pricing/pricing-page.html index ea68b74eb..ee006b2d6 100644 --- a/apps/client/src/app/pages/pricing/pricing-page.html +++ b/apps/client/src/app/pages/pricing/pricing-page.html @@ -326,16 +326,43 @@

- If you plan to open an account at DEGIRO, finpension, - frankly, Interactive Brokers, Swissquote, - VIAC, or Zak, please - If you plan to open an account at +   + @for ( + broker of referralBrokers; + track broker; + let i = $index; + let last = $last + ) { + {{ broker }} + @if (last) { + , + } @else { + @if (i === referralBrokers.length - 2) { +   + or +   + } @else { + , + } + } + } + please +   + contact us - to use our referral link and get a Ghostfolio Premium membership for - one year. Looking for a student discount? Request it - here - with your university e-mail address. +   + to use our referral link and get a Ghostfolio Premium membership + for one year. Looking for a student discount? +   + Request it +   + here +   + with your university e-mail address.

From ba1ee013d7868f549427df92bc379ab072bf372a Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Thu, 16 Oct 2025 20:51:39 +0200 Subject: [PATCH 26/37] Bugfix/fix word wrap in menus of activities table (#5764) * Fix word wrap * Update changelog --- CHANGELOG.md | 1 + .../activities-table/activities-table.component.html | 12 ++++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fb55409a..a024cc722 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Respected the include indices flag in the search functionality of the _Financial Modeling Prep_ service - Fixed an issue where the scroll position was not restored when changing pages +- Fixed the word wrap in the menus of the activities table component - Fixed the dark mode in the _As seen in_ section on the landing page ## 2.208.0 - 2025-10-11 diff --git a/libs/ui/src/lib/activities-table/activities-table.component.html b/libs/ui/src/lib/activities-table/activities-table.component.html index 472c24e2b..8079a6258 100644 --- a/libs/ui/src/lib/activities-table/activities-table.component.html +++ b/libs/ui/src/lib/activities-table/activities-table.component.html @@ -361,7 +361,11 @@ } - + @if (hasPermissionToCreateActivity) { } - + + + @for (dateRange of dateRangeOptions; track dateRange.value) { + + } +