From 3b5fa88ca83d1c7abb9776503d60b9ea330fba83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Mart=C3=ADn?= Date: Fri, 12 Sep 2025 20:29:56 +0200 Subject: [PATCH 01/11] Feature/move holdings table into holdings section on public page (#5508) * Move holdings table into holdings section on public page * Update changelog --- CHANGELOG.md | 1 + .../src/app/pages/public/public-page.html | 20 ++++++++----------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f862df714..ca0d45fb9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Moved the holdings table into the holdings section on the public page - Refactored the login with access token dialog component to standalone ## 2.198.0 - 2025-09-11 diff --git a/apps/client/src/app/pages/public/public-page.html b/apps/client/src/app/pages/public/public-page.html index 7d8636abb..004149ca3 100644 --- a/apps/client/src/app/pages/public/public-page.html +++ b/apps/client/src/app/pages/public/public-page.html @@ -77,6 +77,14 @@ [keys]="['symbol']" [showLabels]="deviceType !== 'mobile'" /> + @@ -195,18 +203,6 @@ } -
-
- -
-

From 7857255ca3a9417d946a785fe8b7503590403650 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 13 Sep 2025 16:11:55 +0200 Subject: [PATCH 02/11] Feature/improve search in Yahoo Finance service (#5518) * Improve search * Update changelog --- CHANGELOG.md | 1 + .../yahoo-finance/yahoo-finance.service.ts | 43 ++++++++++--------- 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ca0d45fb9..c824e35c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Improved the search in the _Yahoo Finance_ service - Moved the holdings table into the holdings section on the public page - Refactored the login with access token dialog component to standalone diff --git a/apps/api/src/services/data-provider/yahoo-finance/yahoo-finance.service.ts b/apps/api/src/services/data-provider/yahoo-finance/yahoo-finance.service.ts index eb6f85d73..40298de15 100644 --- a/apps/api/src/services/data-provider/yahoo-finance/yahoo-finance.service.ts +++ b/apps/api/src/services/data-provider/yahoo-finance/yahoo-finance.service.ts @@ -24,6 +24,7 @@ import { import { Injectable, Logger } from '@nestjs/common'; import { DataSource, SymbolProfile } from '@prisma/client'; import { addDays, format, isSameDay } from 'date-fns'; +import { uniqBy } from 'lodash'; import YahooFinance from 'yahoo-finance2'; import { ChartResultArray } from 'yahoo-finance2/esm/src/modules/chart'; import { @@ -290,7 +291,9 @@ export class YahooFinanceService implements DataProviderInterface { try { marketData = await this.yahooFinance.quote( - quotes.map(({ symbol }) => { + uniqBy(quotes, ({ symbol }) => { + return symbol; + }).map(({ symbol }) => { return symbol; }) ); @@ -300,35 +303,35 @@ export class YahooFinanceService implements DataProviderInterface { } } - for (const marketDataItem of marketData) { - const quote = quotes.find((currentQuote) => { - return currentQuote.symbol === marketDataItem.symbol; - }); - - const symbol = - this.yahooFinanceDataEnhancerService.convertFromYahooFinanceSymbol( - marketDataItem.symbol - ); - + for (const { + currency, + longName, + quoteType, + shortName, + symbol + } of marketData) { const { assetClass, assetSubClass } = this.yahooFinanceDataEnhancerService.parseAssetClass({ - quoteType: quote.quoteType, - shortName: quote.shortname + quoteType, + shortName }); items.push({ assetClass, assetSubClass, - symbol, - currency: marketDataItem.currency, + currency, dataProviderInfo: this.getDataProviderInfo(), dataSource: this.getName(), name: this.yahooFinanceDataEnhancerService.formatName({ - longName: quote.longname, - quoteType: quote.quoteType, - shortName: quote.shortname, - symbol: quote.symbol - }) + longName, + quoteType, + shortName, + symbol + }), + symbol: + this.yahooFinanceDataEnhancerService.convertFromYahooFinanceSymbol( + symbol + ) }); } } catch (error) { From 28f28e737f4c77e69b28879715e8c2a686a22956 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 13 Sep 2025 17:30:48 +0200 Subject: [PATCH 03/11] Task/refactor imports of built-in Node.js core modules (#5513) * Prefix crypto imports * Prefix fs imports * Prefix path imports * Update changelog --- CHANGELOG.md | 1 + apps/api/src/app/app.module.ts | 2 +- apps/api/src/app/endpoints/assets/assets.controller.ts | 4 ++-- apps/api/src/app/endpoints/sitemap/sitemap.controller.ts | 4 ++-- .../portfolio/calculator/portfolio-calculator-test-utils.ts | 2 +- .../calculator/roai/portfolio-calculator-btceur.spec.ts | 2 +- .../roai/portfolio-calculator-btcusd-short.spec.ts | 2 +- .../calculator/roai/portfolio-calculator-btcusd.spec.ts | 2 +- ...portfolio-calculator-novn-buy-and-sell-partially.spec.ts | 2 +- .../roai/portfolio-calculator-novn-buy-and-sell.spec.ts | 2 +- apps/api/src/app/redis-cache/redis-cache.service.ts | 2 +- apps/api/src/app/user/user.service.ts | 2 +- apps/api/src/helper/string.helper.ts | 2 +- apps/api/src/middlewares/html-template.middleware.ts | 6 +++--- apps/api/src/services/api-key/api-key.service.ts | 2 +- apps/api/src/services/i18n/i18n.service.ts | 4 ++-- 16 files changed, 21 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c824e35c2..33d4efa74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Improved the search in the _Yahoo Finance_ service - Moved the holdings table into the holdings section on the public page - Refactored the login with access token dialog component to standalone +- Prefixed the `crypto`, `fs` and `path` imports with `node:` ## 2.198.0 - 2025-09-11 diff --git a/apps/api/src/app/app.module.ts b/apps/api/src/app/app.module.ts index 1c48b9702..8596aa0eb 100644 --- a/apps/api/src/app/app.module.ts +++ b/apps/api/src/app/app.module.ts @@ -21,7 +21,7 @@ import { EventEmitterModule } from '@nestjs/event-emitter'; import { ScheduleModule } from '@nestjs/schedule'; import { ServeStaticModule } from '@nestjs/serve-static'; import { StatusCodes } from 'http-status-codes'; -import { join } from 'path'; +import { join } from 'node:path'; import { AccessModule } from './access/access.module'; import { AccountModule } from './account/account.module'; diff --git a/apps/api/src/app/endpoints/assets/assets.controller.ts b/apps/api/src/app/endpoints/assets/assets.controller.ts index 1735cc594..a314b3f19 100644 --- a/apps/api/src/app/endpoints/assets/assets.controller.ts +++ b/apps/api/src/app/endpoints/assets/assets.controller.ts @@ -10,8 +10,8 @@ import { VERSION_NEUTRAL } from '@nestjs/common'; import { Response } from 'express'; -import { readFileSync } from 'fs'; -import { join } from 'path'; +import { readFileSync } from 'node:fs'; +import { join } from 'node:path'; @Controller('assets') export class AssetsController { diff --git a/apps/api/src/app/endpoints/sitemap/sitemap.controller.ts b/apps/api/src/app/endpoints/sitemap/sitemap.controller.ts index f10f0bfd8..fb581c72e 100644 --- a/apps/api/src/app/endpoints/sitemap/sitemap.controller.ts +++ b/apps/api/src/app/endpoints/sitemap/sitemap.controller.ts @@ -8,8 +8,8 @@ import { import { Controller, Get, Res, VERSION_NEUTRAL, Version } from '@nestjs/common'; import { format } from 'date-fns'; import { Response } from 'express'; -import { readFileSync } from 'fs'; -import { join } from 'path'; +import { readFileSync } from 'node:fs'; +import { join } from 'node:path'; import { SitemapService } from './sitemap.service'; diff --git a/apps/api/src/app/portfolio/calculator/portfolio-calculator-test-utils.ts b/apps/api/src/app/portfolio/calculator/portfolio-calculator-test-utils.ts index 6208eb7d7..8850a6874 100644 --- a/apps/api/src/app/portfolio/calculator/portfolio-calculator-test-utils.ts +++ b/apps/api/src/app/portfolio/calculator/portfolio-calculator-test-utils.ts @@ -1,4 +1,4 @@ -import { readFileSync } from 'fs'; +import { readFileSync } from 'node:fs'; export const activityDummyData = { accountId: undefined, diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur.spec.ts index ad64cb383..1f6f9dc2a 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur.spec.ts @@ -20,7 +20,7 @@ import { PerformanceCalculationType } from '@ghostfolio/common/types/performance import { Tag } from '@prisma/client'; import { Big } from 'big.js'; -import { join } from 'path'; +import { join } from 'node:path'; jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => { return { diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-short.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-short.spec.ts index ce1cf3681..a2d7e60d3 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-short.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-short.spec.ts @@ -20,7 +20,7 @@ import { PerformanceCalculationType } from '@ghostfolio/common/types/performance import { Tag } from '@prisma/client'; import { Big } from 'big.js'; -import { join } from 'path'; +import { join } from 'node:path'; jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => { return { diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd.spec.ts index d17fd028a..bdccb23e0 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd.spec.ts @@ -20,7 +20,7 @@ import { PerformanceCalculationType } from '@ghostfolio/common/types/performance import { Tag } from '@prisma/client'; import { Big } from 'big.js'; -import { join } from 'path'; +import { join } from 'node:path'; jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => { return { diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell-partially.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell-partially.spec.ts index 18455477e..4872a1004 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell-partially.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell-partially.spec.ts @@ -20,7 +20,7 @@ import { PerformanceCalculationType } from '@ghostfolio/common/types/performance import { Tag } from '@prisma/client'; import { Big } from 'big.js'; -import { join } from 'path'; +import { join } from 'node:path'; jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => { return { diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell.spec.ts index e20400cb7..e6c71230b 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell.spec.ts @@ -20,7 +20,7 @@ import { PerformanceCalculationType } from '@ghostfolio/common/types/performance import { Tag } from '@prisma/client'; import { Big } from 'big.js'; -import { join } from 'path'; +import { join } from 'node:path'; jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => { return { diff --git a/apps/api/src/app/redis-cache/redis-cache.service.ts b/apps/api/src/app/redis-cache/redis-cache.service.ts index 621199cc9..1ea0a6137 100644 --- a/apps/api/src/app/redis-cache/redis-cache.service.ts +++ b/apps/api/src/app/redis-cache/redis-cache.service.ts @@ -4,9 +4,9 @@ import { AssetProfileIdentifier, Filter } from '@ghostfolio/common/interfaces'; import { CACHE_MANAGER, Cache } from '@nestjs/cache-manager'; import { Inject, Injectable, Logger } from '@nestjs/common'; -import { createHash } from 'crypto'; import Keyv from 'keyv'; import ms from 'ms'; +import { createHash } from 'node:crypto'; @Injectable() export class RedisCacheService { diff --git a/apps/api/src/app/user/user.service.ts b/apps/api/src/app/user/user.service.ts index 10f8d3744..6512fbbc2 100644 --- a/apps/api/src/app/user/user.service.ts +++ b/apps/api/src/app/user/user.service.ts @@ -48,9 +48,9 @@ import { PerformanceCalculationType } from '@ghostfolio/common/types/performance import { Injectable } from '@nestjs/common'; import { EventEmitter2 } from '@nestjs/event-emitter'; import { Prisma, Role, User } from '@prisma/client'; -import { createHmac } from 'crypto'; import { differenceInDays, subDays } from 'date-fns'; import { sortBy, without } from 'lodash'; +import { createHmac } from 'node:crypto'; @Injectable() export class UserService { diff --git a/apps/api/src/helper/string.helper.ts b/apps/api/src/helper/string.helper.ts index 38bac79f1..75f9d00fd 100644 --- a/apps/api/src/helper/string.helper.ts +++ b/apps/api/src/helper/string.helper.ts @@ -1,4 +1,4 @@ -import { randomBytes } from 'crypto'; +import { randomBytes } from 'node:crypto'; export function getRandomString(length: number) { const bytes = randomBytes(length); diff --git a/apps/api/src/middlewares/html-template.middleware.ts b/apps/api/src/middlewares/html-template.middleware.ts index 75ec37480..665b93354 100644 --- a/apps/api/src/middlewares/html-template.middleware.ts +++ b/apps/api/src/middlewares/html-template.middleware.ts @@ -10,8 +10,8 @@ import { DATE_FORMAT, interpolate } from '@ghostfolio/common/helper'; import { Injectable, Logger, NestMiddleware } from '@nestjs/common'; import { format } from 'date-fns'; import { NextFunction, Request, Response } from 'express'; -import * as fs from 'fs'; -import { join } from 'path'; +import { readFileSync } from 'node:fs'; +import { join } from 'node:path'; const title = 'Ghostfolio'; @@ -87,7 +87,7 @@ export class HtmlTemplateMiddleware implements NestMiddleware { this.indexHtmlMap = SUPPORTED_LANGUAGE_CODES.reduce( (map, languageCode) => ({ ...map, - [languageCode]: fs.readFileSync( + [languageCode]: readFileSync( join(__dirname, '..', 'client', languageCode, 'index.html'), 'utf8' ) diff --git a/apps/api/src/services/api-key/api-key.service.ts b/apps/api/src/services/api-key/api-key.service.ts index f70e5330c..b911191dc 100644 --- a/apps/api/src/services/api-key/api-key.service.ts +++ b/apps/api/src/services/api-key/api-key.service.ts @@ -3,7 +3,7 @@ import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service'; import { ApiKeyResponse } from '@ghostfolio/common/interfaces'; import { Injectable } from '@nestjs/common'; -import { pbkdf2Sync } from 'crypto'; +import { pbkdf2Sync } from 'node:crypto'; @Injectable() export class ApiKeyService { diff --git a/apps/api/src/services/i18n/i18n.service.ts b/apps/api/src/services/i18n/i18n.service.ts index a0389ab36..0f1f6239d 100644 --- a/apps/api/src/services/i18n/i18n.service.ts +++ b/apps/api/src/services/i18n/i18n.service.ts @@ -2,8 +2,8 @@ import { DEFAULT_LANGUAGE_CODE } from '@ghostfolio/common/config'; import { Injectable, Logger } from '@nestjs/common'; import * as cheerio from 'cheerio'; -import { readFileSync, readdirSync } from 'fs'; -import { join } from 'path'; +import { readFileSync, readdirSync } from 'node:fs'; +import { join } from 'node:path'; @Injectable() export class I18nService { From 13b3ef20fce5798048fd2f48c7b4ad178074a827 Mon Sep 17 00:00:00 2001 From: Attila Cseh <77381875+csehatt741@users.noreply.github.com> Date: Sat, 13 Sep 2025 17:36:00 +0200 Subject: [PATCH 04/11] Feature/storybook story for entity logo component (#5192) * Set up Storybook story for entity logo component * Update changelog --- CHANGELOG.md | 4 ++ .../entity-logo-image-source.service.ts | 20 +++++++++ .../entity-logo.component.stories.ts | 44 +++++++++++++++++++ .../lib/entity-logo/entity-logo.component.ts | 13 +++++- .../entity-logo-image-source.service.mock.ts | 24 ++++++++++ 5 files changed, 103 insertions(+), 2 deletions(-) create mode 100644 libs/ui/src/lib/entity-logo/entity-logo-image-source.service.ts create mode 100644 libs/ui/src/lib/entity-logo/entity-logo.component.stories.ts create mode 100644 libs/ui/src/lib/mocks/entity-logo-image-source.service.mock.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 33d4efa74..3a557d40f 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 entity logo image component + ### Changed - Improved the search in the _Yahoo Finance_ service diff --git a/libs/ui/src/lib/entity-logo/entity-logo-image-source.service.ts b/libs/ui/src/lib/entity-logo/entity-logo-image-source.service.ts new file mode 100644 index 000000000..9cbea529b --- /dev/null +++ b/libs/ui/src/lib/entity-logo/entity-logo-image-source.service.ts @@ -0,0 +1,20 @@ +import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces'; + +import { Injectable } from '@angular/core'; + +@Injectable({ + // Required to allow mocking in Storybook + providedIn: 'root' +}) +export class EntityLogoImageSourceService { + public getLogoUrlByAssetProfileIdentifier({ + dataSource, + symbol + }: AssetProfileIdentifier) { + return `../api/v1/logo/${dataSource}/${symbol}`; + } + + public getLogoUrlByUrl(url: string) { + return `../api/v1/logo?url=${url}`; + } +} diff --git a/libs/ui/src/lib/entity-logo/entity-logo.component.stories.ts b/libs/ui/src/lib/entity-logo/entity-logo.component.stories.ts new file mode 100644 index 000000000..6c89718bd --- /dev/null +++ b/libs/ui/src/lib/entity-logo/entity-logo.component.stories.ts @@ -0,0 +1,44 @@ +import { CommonModule } from '@angular/common'; +import { importProvidersFrom } from '@angular/core'; +import { provideNoopAnimations } from '@angular/platform-browser/animations'; +import { applicationConfig, Meta, StoryObj } from '@storybook/angular'; + +import { EntityLogoImageSourceServiceMock } from '../mocks/entity-logo-image-source.service.mock'; +import { EntityLogoImageSourceService } from './entity-logo-image-source.service'; +import { GfEntityLogoComponent } from './entity-logo.component'; + +export default { + title: 'Entity Logo', + component: GfEntityLogoComponent, + decorators: [ + applicationConfig({ + providers: [ + provideNoopAnimations(), + importProvidersFrom(CommonModule), + { + provide: EntityLogoImageSourceService, + useValue: new EntityLogoImageSourceServiceMock() + } + ] + }) + ] +} as Meta; + +type Story = StoryObj; + +export const LogoByAssetProfileIdentifier: Story = { + args: { + dataSource: 'YAHOO', + size: 'large', + symbol: 'AAPL', + tooltip: 'Apple Inc.' + } +}; + +export const LogoByUrl: Story = { + args: { + size: 'large', + tooltip: 'Ghostfolio', + url: 'https://ghostfol.io' + } +}; diff --git a/libs/ui/src/lib/entity-logo/entity-logo.component.ts b/libs/ui/src/lib/entity-logo/entity-logo.component.ts index 7598fb4d5..212e232be 100644 --- a/libs/ui/src/lib/entity-logo/entity-logo.component.ts +++ b/libs/ui/src/lib/entity-logo/entity-logo.component.ts @@ -1,3 +1,5 @@ +import { EntityLogoImageSourceService } from '@ghostfolio/ui/entity-logo/entity-logo-image-source.service'; + import { CommonModule } from '@angular/common'; import { CUSTOM_ELEMENTS_SCHEMA, @@ -25,11 +27,18 @@ export class GfEntityLogoComponent implements OnChanges { public src: string; + public constructor( + private readonly imageSourceService: EntityLogoImageSourceService + ) {} + public ngOnChanges() { if (this.dataSource && this.symbol) { - this.src = `../api/v1/logo/${this.dataSource}/${this.symbol}`; + this.src = this.imageSourceService.getLogoUrlByAssetProfileIdentifier({ + dataSource: this.dataSource, + symbol: this.symbol + }); } else if (this.url) { - this.src = `../api/v1/logo?url=${this.url}`; + this.src = this.imageSourceService.getLogoUrlByUrl(this.url); } } } diff --git a/libs/ui/src/lib/mocks/entity-logo-image-source.service.mock.ts b/libs/ui/src/lib/mocks/entity-logo-image-source.service.mock.ts new file mode 100644 index 000000000..3f4dbbef7 --- /dev/null +++ b/libs/ui/src/lib/mocks/entity-logo-image-source.service.mock.ts @@ -0,0 +1,24 @@ +import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces'; + +import { DataSource } from '@prisma/client'; + +export class EntityLogoImageSourceServiceMock { + public getLogoUrlByAssetProfileIdentifier({ + dataSource, + symbol + }: AssetProfileIdentifier) { + if (dataSource === DataSource.YAHOO && symbol === 'AAPL') { + return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAJa0lEQVR4nM2bW2wU1xnHf3vx2t61za5nL/bGKwx2jQnwUh5Q3bqyHYhzIVLUFoIaSKtGSrkkNaQJqlQSKaHQFygQJamUSomaRKpSlYekqSF1HGix6EOktIldOQs0OCEs3ssw48Ve8F77MGuwza4ve2Y3/KR92Ln8z/d9OnPmnG/OZ/jss2FKRA3QCiwDPEAtUAEkgWvAGKAAF4HPgWgpjDIXUdsF3AN0Zn/NVmuFoazMgtlswmw2YTAYyGQgnU6TSqVIpdIkEnFisRsZ4AJwGjgFfAiEi2GkQeceUAn8CPgJ0GG315hsNis2m5XycsuihCYn40xMxJiYiKGq0RRaMP4I/AW4rpfBegXAAfQAu+z2JU6HowabzYbBoIc0ZDIwMTGBokRR1bEI8ApwDO2REUI0AGbgKeB5t1uyS1ItZrNJ1KY5SSZTyPJVQiFZBfYDL6GNIwUhEoDvAr+XJMcaSXIsuouLEo/HiUQUZFkZBHYCA4XoGAu4xwS8APzD5/Ou8Xo9JXcewGKx4PV68Pm8a9DGhxezti2KxfYAF/COJDk63W5n0bv7QkkmU4RCEWRZOQU8wiLeGIvpAY3AgNvt7PR6PXeM8wBmswmv14Pb7exEexQaF3rvQgOwCjhbV+dq8XicBZhYGjweJ3V1rhbgLJrN87KQADQBJ71eT73LJYnYVxJcLgmv11MPnASa57t+vgC4gJP19e4GSXLoYV9JkCQH9fXuBuAEmg95mSsAZcBxt9vZ7HTW6mlfXsbHJzh27CU6Ojppbm7h4sWLBWs5nbW43c5m4DiaLzmZay2wX5Ic7aV65s+dO8fu3U8zNDQEgM1mo7q6WkjT43GSSqXaZVnZD/wq1zX5esD3gGfr6ubsPbpx/vx5tm597KbzAK2tK6itFe95WR+eRfPpNnIFwAy86vN5jUZjIfOkxTE5Ocnu3U8zOjo64/hDD21Ej/aNRiM+n9cIvEqOHp+rhR5Jcqyx22uEG18IfX19DA4OzjhWX1/Pww8/rFsbdnsNkuRYA/xi9rnZAagB9pVq0AN4//3eGf+rq6s5cuSwLt1/Ok6nA+A5NB9vMjsAO10uyW6x5B00dSWRSPDpp/+5+b+lpYW3336LtrY23duyWCy4XJIdbeF0k+nPRCWwR5Lsujeej3Q6zbJly1m6dCnd3d1s27YVs7l4SSpJshMOy3uAo8ANmLkY2mK31/zJ5/Pq1qAsy/T1fcjg4CCKomK1VrJy5Uo6Ojpoalp+2/VDQ0P09X2I33+O8fFrlJdX4PXW09bWRldXJ+Xl5cI2ffVVgLGx6BbgHZgZgL81NjY8UF1dJdxIMpnktdf+wOuvv0EoFLrtfEVFBV1dXWzc+AB33dXA4OAg7733Vz7++GMymUxOzdbWVvbufYb169cL2Xbt2jgjI1/3Ag/CrQC4gMDq1SvMBsE81uTkJDt3PklfX5+QTj4OHjzA1q2PFnx/JpNhaMifBLxAeGoQvMdurxF2PpVK0dOzp2jOA+zb9xx+v7/g+w0GA3Z7jRktY33zLdBls1mFjXvrrbfp7e2d/8ICMRgMHDjwG5qamoR0sr52wa23QGdVlVgAotEoL7/8ipDGfDz//HM8+uiPhXWyAegArQdYgeUWi1he78SJkzkHPL3o7r6Xxx//mS5a2RxmE1BpBJorKsqFJ939/f2iEnkxmUw89dSTumpmff6WEWjVI6s7MvKlsEY+li9fzqpVC8pwLZiszyuMQH1ZmfjsKxodE9bIR3NzEyaTvknY7Iyz3gg49BCfnIwLa+SjslL8DTWbrM8OI1Au+v4HdFm750NRruquaTQaACqMQCrP7HNR2Gw2cZE8DA9/TiwW01Uz63PSCFxLp9PCgnb7EmGNfIyOjnL69GldNbM+jxuBaDqdEhZctmyZsMZcHDlyTNdekPU5agTkZFI8AOvWrRPWmAu/309Pz24SiYQuelmfw0bgcz1G8La271BWVtxM0gcf/J0nntjO5cuXhbWyPp8zAhdu3JgUHgQaGxtZu/bbwobNR39/P5s2PSKsk/X5vBGIAV/E4+K9YMuWLcIaCyGZLHhDCKBtrgC+AK5PvbxPjY+LDzD33deNz+cT1pmPzZs3Cd2f9fUU3MoHfDQxIR4Aq9XKrl07hHXmora2lm3btgppZH2dEYB+VY0m8+XjFsPmzZtZvXq1sE4+duzYjsfjKfj+TCaDqkaTQD/cCkAY6NPjMTCbzezf/0JR0tt3372Sxx7bJqSR9bEPCMHMDyNvqqo+K7q1a9eyd++zumhNUVVl4/DhQ1RWVgrpZH18c+r/9AC8q6rRsF4Tje3bfy78rE5hNps5evSocE4gkUigqtEQ8O5N7WnnrwO/k2X1t3p9Fn/xxReIxa5z/PjxGcdtNhvr199De3s7DQ0NxONx/H4/J06c4JNP/j3j2qoqGwcPHuTeezcI2yPLKsARpm21nb1Nrgb4csWKJl2/D/b29nLmzACJRIKWlhY2bFifc+2QyWQYGBjg7Nl/oSgqdXUeNm58kObmebf6zEs8nsDv/58KLGXaTvRc+wR/KUmOQ15v4SPtnUggEESWlWeAw9OP58piHJNlZUhVS7JdvySoanRqS+2x2edyBSAJ7Lh0KZDWI0/wTZNOp7l0KZBG+yx+2xw6Xx5rADg0OlqUGoWSEgyGAQ6RZzP1XIm8fbKsnAkGI8WwqyQEgxEiEWUAbWdITuYKQAL4YSgUuRCJ6J+ULDaRyFVCocgF4AdA3qXufKncMHD/lSuhr2VZuDijZMiywpUrocvA/cyzc3whuewLwH2BQPBKOCzrYV9RCYdlAoHgFaAbzfY5WWgy/79A2+ho+NydPCYEgxFGR8MXgDY0m+dlMV8zRoD2UChyKhAIokciVS+SyRSBQJBQKHIKrZRnZKH3LvZzTgjYIMvK/uHh8+k7YbKkqlGGh8+ns/uBN5Bd5i4UkaKpduBVSXKsdjprKdXewini8QSRyFVkWRlCm+ScKURHj7K5HuDXLpfkkCQHenxpnotEIoksK4TDsgIcQJvefiNlc9OpAZ4EehyOJW6HYwlWq1XXwslYLIaijKEoYyE0p19Gh/riYpTObgJ+Cnx/qnS2qsrKYrfgxONxxsdnlM7+E6109s/cgaWzufCg7cTqAjqMRmNzRUU5ZWVllJWZMJnyFU8nuHFjknQ6PVU8/VH2FyyGkcUMwGxqgJVoJW11gARYuL18fgQYpkTl8/8HqhBYlUKrXOwAAAAASUVORK5CYII='; + } + + return ''; + } + + public getLogoUrlByUrl(url: string) { + if (url === 'https://ghostfol.io') { + return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAAP1BMVEU2z8v////x/Pspzcn0/Px73drR8/KC3tz6/v6e5eOl5+WM4d7n+PhI08/i9/eu6edV1dJk2NVx2teV4+G87evvttLSAAABDElEQVRYhe2V3Y6EIAyFrQXlTwHd93/WpeBsdiKbtF7tJJwbCKFfDlKO0zQ0NPQJwqrn1ZO2zlk9PWPgusClRcsJmHb4pWUTItDDu4zMBJ5w0yog4HGvB0gCgOoBZrYFdL16AMsmmD5AcQ3ofj3AwbOAX38BIhMw02ZjtQ+tLnht66mCAGA2eka1G3eabUZwD/PLbeuHenKMBODVV0Dru/TTQLgKAc1BvQ/9yAEkOnlon66oehEBIHp7dbSyPoIc0NEADMDnAa4wAvVK+CADRGx/VpNSi+iF3jMTUH4rJQ0aISPmVk+JoJiZeJw1QnaqL2OmWKSFkxnrZWsbXK4TzO5tmS+8TYaGhv6xvgEEfAgHGc7HRgAAAABJRU5ErkJggg=='; + } + + return ''; + } +} From b26d44a58809d938b3d40c23aec38c45a9d56051 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sun, 14 Sep 2025 09:45:17 +0200 Subject: [PATCH 05/11] Feature/extend personal finance tools 20250914 (#5522) * Add Amsflow * Add BudgetPulse * Add CountAbout * Add Honeydue * Add MoneyWiz * Add SplashMoney * Extend tags --- .../product-page.component.ts | 12 +++-- libs/common/src/lib/personal-finance-tools.ts | 46 +++++++++++++++++++ 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts b/apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts index caf29e7a0..511cf672d 100644 --- a/apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts +++ b/apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts @@ -76,7 +76,9 @@ export class GfProductPageComponent implements OnInit { this.tags = [ this.product1.name, + this.product1.origin, this.product2.name, + this.product2.origin, $localize`Alternative`, $localize`App`, $localize`Budgeting`, @@ -96,8 +98,12 @@ export class GfProductPageComponent implements OnInit { $localize`Wealth`, $localize`Wealth Management`, `WealthTech` - ].sort((a, b) => { - return a.localeCompare(b, undefined, { sensitivity: 'base' }); - }); + ] + .filter((item) => { + return !!item; + }) + .sort((a, b) => { + return a.localeCompare(b, undefined, { sensitivity: 'base' }); + }); } } diff --git a/libs/common/src/lib/personal-finance-tools.ts b/libs/common/src/lib/personal-finance-tools.ts index 20adb7a77..68d6dfa22 100644 --- a/libs/common/src/lib/personal-finance-tools.ts +++ b/libs/common/src/lib/personal-finance-tools.ts @@ -33,6 +33,16 @@ export const personalFinanceTools: Product[] = [ origin: 'Switzerland', slogan: 'Simplicity for Complex Wealth' }, + { + founded: 2023, + hasFreePlan: false, + hasSelfHostingAbility: false, + key: 'amsflow', + name: 'Amsflow Portfolio', + origin: 'Singapore', + pricingPerYear: '$228', + slogan: 'Portfolio Visualizer' + }, { founded: 2018, hasFreePlan: true, @@ -97,6 +107,12 @@ export const personalFinanceTools: Product[] = [ pricingPerYear: '$100', slogan: 'Stock Portfolio Tracker for Smart Investors' }, + { + key: 'budgetpulse', + name: 'BudgetPulse', + origin: 'United States', + slogan: 'Giving life to your finance!' + }, { founded: 2007, hasFreePlan: false, @@ -180,6 +196,15 @@ export const personalFinanceTools: Product[] = [ pricingPerYear: '$95', slogan: 'Do money better with Copilot' }, + { + founded: 2014, + hasFreePlan: false, + key: 'countabout', + name: 'CountAbout', + origin: 'United States', + pricingPerYear: '$9.99', + slogan: 'Customizable and Secure Personal Finance App' + }, { founded: 2023, hasFreePlan: false, @@ -427,6 +452,14 @@ export const personalFinanceTools: Product[] = [ slogan: 'Die All-in-One Lösung für dein Vermögen.', useAnonymously: true }, + { + founded: 2017, + hasSelfHostingAbility: false, + key: 'honeydue', + name: 'Honeydue', + origin: 'United States', + slogan: 'Finance App for Couples' + }, { founded: 2022, key: 'income-reign', @@ -608,6 +641,13 @@ export const personalFinanceTools: Product[] = [ origin: 'Germany', slogan: 'Dein smarter Finance Assistant' }, + { + key: 'moneywiz', + name: 'MoneyWiz', + origin: 'United States', + pricingPerYear: '$29.99', + slogan: 'Get money management superpowers' + }, { hasFreePlan: false, hasSelfHostingAbility: false, @@ -859,6 +899,12 @@ export const personalFinanceTools: Product[] = [ pricingPerYear: '$80', slogan: 'Simple and powerful portfolio tracker' }, + { + key: 'splashmoney', + name: 'SplashMoney', + origin: 'United States', + slogan: 'Manage your money anytime, anywhere.' + }, { founded: 2019, hasSelfHostingAbility: false, From f7f942d08d63eb80f49bb7afbb175c83f62244c8 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sun, 14 Sep 2025 09:45:55 +0200 Subject: [PATCH 06/11] Bugfix/fix pagination issue in market data and user endpoint by adding secondary sort criterion (#5521) * Add id as secondary sort criterion to ensure consistent ordering * Refactoring * Update changelog --- CHANGELOG.md | 5 ++++ apps/api/src/app/admin/admin.service.ts | 34 +++++++++++++++---------- apps/api/src/app/order/order.service.ts | 12 ++++----- 3 files changed, 31 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a557d40f..962bcf07f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Refactored the login with access token dialog component to standalone - Prefixed the `crypto`, `fs` and `path` imports with `node:` +### Fixed + +- Fixed a pagination issue in the market data endpoint by adding `id` as a secondary sort criterion to ensure consistent ordering in the admin control panel +- Fixed a pagination issue in the user endpoint by adding `id` as a secondary sort criterion to ensure consistent ordering in the admin control panel + ## 2.198.0 - 2025-09-11 ### Changed diff --git a/apps/api/src/app/admin/admin.service.ts b/apps/api/src/app/admin/admin.service.ts index 2918d9d7d..11f6f0599 100644 --- a/apps/api/src/app/admin/admin.service.ts +++ b/apps/api/src/app/admin/admin.service.ts @@ -192,7 +192,7 @@ export class AdminService { filters, presetId, sortColumn, - sortDirection, + sortDirection = 'asc', skip, take = Number.MAX_SAFE_INTEGER }: { @@ -262,11 +262,13 @@ export class AdminService { orderBy = [{ [sortColumn]: sortDirection }]; if (sortColumn === 'activitiesCount') { - orderBy = { - activities: { - _count: sortDirection + orderBy = [ + { + activities: { + _count: sortDirection + } } - }; + ]; } } @@ -275,10 +277,10 @@ export class AdminService { try { const symbolProfileResult = await Promise.all([ extendedPrismaClient.symbolProfile.findMany({ - orderBy, skip, take, where, + orderBy: [...orderBy, { id: sortDirection }], select: { _count: { select: { @@ -817,17 +819,21 @@ export class AdminService { skip?: number; take?: number; }): Promise { - let orderBy: Prisma.UserOrderByWithRelationInput = { - createdAt: 'desc' - }; + let orderBy: Prisma.Enumerable = [ + { createdAt: 'desc' } + ]; + let where: Prisma.UserWhereInput; if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) { - orderBy = { - analytics: { - lastRequestAt: 'desc' + orderBy = [ + { + analytics: { + lastRequestAt: 'desc' + } } - }; + ]; + where = { NOT: { analytics: null @@ -836,10 +842,10 @@ export class AdminService { } const usersWithAnalytics = await this.prismaService.user.findMany({ - orderBy, skip, take, where, + orderBy: [...orderBy, { id: 'desc' }], select: { _count: { select: { accounts: true, activities: true } diff --git a/apps/api/src/app/order/order.service.ts b/apps/api/src/app/order/order.service.ts index fd50f5e74..11579bbf1 100644 --- a/apps/api/src/app/order/order.service.ts +++ b/apps/api/src/app/order/order.service.ts @@ -325,7 +325,7 @@ export class OrderService { includeDrafts = false, skip, sortColumn, - sortDirection, + sortDirection = 'asc', startDate, take = Number.MAX_SAFE_INTEGER, types, @@ -347,9 +347,9 @@ export class OrderService { withExcludedAccountsAndActivities?: boolean; }): Promise { let orderBy: Prisma.Enumerable = [ - { date: 'asc' }, - { id: 'asc' } + { date: 'asc' } ]; + const where: Prisma.OrderWhereInput = { userId }; if (endDate || startDate) { @@ -483,7 +483,7 @@ export class OrderService { } if (sortColumn) { - orderBy = [{ [sortColumn]: sortDirection }, { id: sortDirection }]; + orderBy = [{ [sortColumn]: sortDirection }]; } if (types) { @@ -506,7 +506,6 @@ export class OrderService { const [orders, count] = await Promise.all([ this.orders({ - orderBy, skip, take, where, @@ -519,7 +518,8 @@ export class OrderService { // eslint-disable-next-line @typescript-eslint/naming-convention SymbolProfile: true, tags: true - } + }, + orderBy: [...orderBy, { id: sortDirection }] }), this.prismaService.order.count({ where }) ]); From 387c2bb6f3faff9b4746f72add90ae928bd8caf6 Mon Sep 17 00:00:00 2001 From: David Requeno <108202767+DavidReque@users.noreply.github.com> Date: Sun, 14 Sep 2025 02:00:18 -0600 Subject: [PATCH 07/11] Task/migrate to prisma configuration file (#5489) * Migrate to prisma configuration file * Updated changelog --- CHANGELOG.md | 1 + Dockerfile | 18 ++++++++++-------- package.json | 3 --- prisma.config.ts | 11 +++++++++++ 4 files changed, 22 insertions(+), 11 deletions(-) create mode 100644 prisma.config.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 962bcf07f..6c84775e8 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 - Improved the search in the _Yahoo Finance_ service - Moved the holdings table into the holdings section on the public page +- Migrated to the _Prisma Configuration File_ approach (`prisma.config.ts`) - Refactored the login with access token dialog component to standalone - Prefixed the `crypto`, `fs` and `path` imports with `node:` diff --git a/Dockerfile b/Dockerfile index c740413ea..be1bb53ea 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,7 +17,8 @@ COPY ./CHANGELOG.md CHANGELOG.md COPY ./LICENSE LICENSE COPY ./package.json package.json COPY ./package-lock.json package-lock.json -COPY ./prisma/schema.prisma prisma/schema.prisma +COPY ./prisma.config.ts prisma.config.ts +COPY ./prisma/schema.prisma prisma/ RUN npm install @@ -25,8 +26,8 @@ RUN npm install COPY ./decorate-angular-cli.js decorate-angular-cli.js RUN node decorate-angular-cli.js -COPY ./apps apps -COPY ./libs libs +COPY ./apps apps/ +COPY ./libs libs/ COPY ./jest.config.ts jest.config.ts COPY ./jest.preset.js jest.preset.js COPY ./nx.json nx.json @@ -40,14 +41,15 @@ RUN npm run build:production WORKDIR /ghostfolio/dist/apps/api # package.json was generated by the build process, however the original # package-lock.json needs to be used to ensure the same versions -COPY ./package-lock.json /ghostfolio/dist/apps/api/package-lock.json +COPY ./package-lock.json /ghostfolio/dist/apps/api/ RUN npm install -COPY prisma /ghostfolio/dist/apps/api/prisma +COPY prisma.config.ts /ghostfolio/dist/apps/api/ +COPY prisma /ghostfolio/dist/apps/api/prisma/ # Overwrite the generated package.json with the original one to ensure having # all the scripts -COPY package.json /ghostfolio/dist/apps/api +COPY package.json /ghostfolio/dist/apps/api/ RUN npm run database:generate-typings # Image to run, copy everything needed from builder @@ -60,8 +62,8 @@ RUN apt-get update && apt-get install -y --no-install-suggests \ openssl \ && rm -rf /var/lib/apt/lists/* -COPY --chown=node:node --from=builder /ghostfolio/dist/apps /ghostfolio/apps -COPY --chown=node:node ./docker/entrypoint.sh /ghostfolio/entrypoint.sh +COPY --chown=node:node --from=builder /ghostfolio/dist/apps /ghostfolio/apps/ +COPY --chown=node:node ./docker/entrypoint.sh /ghostfolio/ WORKDIR /ghostfolio/apps/api EXPOSE ${PORT:-3333} USER node diff --git a/package.json b/package.json index 9a68c6cc6..99d924332 100644 --- a/package.json +++ b/package.json @@ -209,8 +209,5 @@ }, "engines": { "node": ">=22.18.0" - }, - "prisma": { - "seed": "node prisma/seed.mts" } } diff --git a/prisma.config.ts b/prisma.config.ts new file mode 100644 index 000000000..24da6d886 --- /dev/null +++ b/prisma.config.ts @@ -0,0 +1,11 @@ +import 'dotenv/config'; +import { join } from 'node:path'; +import { defineConfig } from 'prisma/config'; + +export default defineConfig({ + migrations: { + path: join('prisma', 'migrations'), + seed: `node ${join('prisma', 'seed.mts')}` + }, + schema: join('prisma', 'schema.prisma') +}); From b3d4297d8341e6652f88963b1abf4fee4aea1771 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sun, 14 Sep 2025 10:02:50 +0200 Subject: [PATCH 08/11] Release 2.199.0-beta.0 (#5523) --- 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 6c84775e8..54983a2d0 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.199.0-beta.0 - 2025-09-14 ### Added diff --git a/package-lock.json b/package-lock.json index efc544d63..d9f440703 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ghostfolio", - "version": "2.198.0", + "version": "2.199.0-beta.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ghostfolio", - "version": "2.198.0", + "version": "2.199.0-beta.0", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/package.json b/package.json index 99d924332..c701a5fbd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ghostfolio", - "version": "2.198.0", + "version": "2.199.0-beta.0", "homepage": "https://ghostfol.io", "license": "AGPL-3.0", "repository": "https://github.com/ghostfolio/ghostfolio", From 67da647d532efff8924737db880f311c0e239a6a Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sun, 14 Sep 2025 10:38:27 +0200 Subject: [PATCH 09/11] Feature/upgrade yahoo-finance2 to version 3.10.0 (#5469) * Upgrade yahoo-finance2 to version 3.10.0 * Update changelog --- CHANGELOG.md | 3 +- .../yahoo-finance/yahoo-finance.service.ts | 42 +++++++++++-------- package-lock.json | 8 ++-- package.json | 2 +- 4 files changed, 31 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 54983a2d0..79389d1f6 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). -## 2.199.0-beta.0 - 2025-09-14 +## Unreleased ### Added @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Migrated to the _Prisma Configuration File_ approach (`prisma.config.ts`) - Refactored the login with access token dialog component to standalone - Prefixed the `crypto`, `fs` and `path` imports with `node:` +- Upgraded `yahoo-finance2` from version `3.8.0` to `3.10.0` ### Fixed diff --git a/apps/api/src/services/data-provider/yahoo-finance/yahoo-finance.service.ts b/apps/api/src/services/data-provider/yahoo-finance/yahoo-finance.service.ts index 40298de15..390449d78 100644 --- a/apps/api/src/services/data-provider/yahoo-finance/yahoo-finance.service.ts +++ b/apps/api/src/services/data-provider/yahoo-finance/yahoo-finance.service.ts @@ -35,6 +35,10 @@ import { Quote, QuoteResponseArray } from 'yahoo-finance2/esm/src/modules/quote'; +import { + Price, + QuoteSummaryResult +} from 'yahoo-finance2/esm/src/modules/quoteSummary'; import { SearchQuoteNonYahoo } from 'yahoo-finance2/esm/src/modules/search'; @Injectable() @@ -191,10 +195,7 @@ export class YahooFinanceService implements DataProviderInterface { ); try { - let quotes: Pick< - Quote, - 'currency' | 'marketState' | 'regularMarketPrice' | 'symbol' - >[] = []; + let quotes: Price[] | Quote[] = []; try { quotes = await this.yahooFinance.quote(yahooFinanceSymbols); @@ -357,23 +358,28 @@ export class YahooFinanceService implements DataProviderInterface { private async getQuotesWithQuoteSummary(aYahooFinanceSymbols: string[]) { const quoteSummaryPromises = aYahooFinanceSymbols.map((symbol) => { - return this.yahooFinance.quoteSummary(symbol).catch(() => { - Logger.error( - `Could not get quote summary for ${symbol}`, - 'YahooFinanceService' - ); - return null; - }); + return this.yahooFinance.quoteSummary(symbol); }); - const quoteSummaryItems = await Promise.all(quoteSummaryPromises); + const settledResults = await Promise.allSettled(quoteSummaryPromises); + + return settledResults + .filter( + (result): result is PromiseFulfilledResult => { + if (result.status === 'rejected') { + Logger.error( + `Could not get quote summary: ${result.reason}`, + 'YahooFinanceService' + ); - return quoteSummaryItems - .filter((item) => { - return item !== null; - }) - .map(({ price }) => { - return price; + return false; + } + + return true; + } + ) + .map(({ value }) => { + return value.price; }); } } diff --git a/package-lock.json b/package-lock.json index d9f440703..a7a9df6c7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -93,7 +93,7 @@ "svgmap": "2.12.2", "twitter-api-v2": "1.23.0", "uuid": "11.1.0", - "yahoo-finance2": "3.8.0", + "yahoo-finance2": "3.10.0", "zone.js": "0.15.1" }, "devDependencies": { @@ -42071,9 +42071,9 @@ } }, "node_modules/yahoo-finance2": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/yahoo-finance2/-/yahoo-finance2-3.8.0.tgz", - "integrity": "sha512-em11JOlfSg23wevm4kXs1+A/CoSWD9eg7/hKRU3zKWuPknCfE4NkIhGVb601Nokid+KPE8Q0eoXK4qgLsMIjKA==", + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/yahoo-finance2/-/yahoo-finance2-3.10.0.tgz", + "integrity": "sha512-0mnvefEAapMS6M3tnqLmQlyE2W38AQqByaTS09l2dawLaVU7NNc0hJ4qI4F3qi3C7MU+ZWAb8DFVKpW6Zsj0Nw==", "license": "MIT", "dependencies": { "@deno/shim-deno": "~0.18.0", diff --git a/package.json b/package.json index c701a5fbd..ac62690b9 100644 --- a/package.json +++ b/package.json @@ -139,7 +139,7 @@ "svgmap": "2.12.2", "twitter-api-v2": "1.23.0", "uuid": "11.1.0", - "yahoo-finance2": "3.8.0", + "yahoo-finance2": "3.10.0", "zone.js": "0.15.1" }, "devDependencies": { From 55e25664a4edba734fbc2058ed2d347029107960 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sun, 14 Sep 2025 17:53:05 +0200 Subject: [PATCH 10/11] Feature/extend section of performance calculation method in FAQ (#5528) * Extend ROAI content by dividends * Update changelog --- CHANGELOG.md | 1 + apps/client/src/app/pages/faq/overview/faq-overview-page.html | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 79389d1f6..29d5da1ab 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 content of the performance calculation method by dividends on the Frequently Asked Questions (FAQ) page - Added a _Storybook_ story for the entity logo image component ### Changed diff --git a/apps/client/src/app/pages/faq/overview/faq-overview-page.html b/apps/client/src/app/pages/faq/overview/faq-overview-page.html index 24caf8d97..3b43ac096 100644 --- a/apps/client/src/app/pages/faq/overview/faq-overview-page.html +++ b/apps/client/src/app/pages/faq/overview/faq-overview-page.html @@ -52,7 +52,8 @@ calculation method based on the average amount of capital invested over time. ROAI aims to provide a more insightful view of investment performance than simpler approaches, especially when contributions are - made over time. From 4d2e7bf8aae3ecc0144856054814bcc7967cd9f4 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sun, 14 Sep 2025 17:54:39 +0200 Subject: [PATCH 11/11] Release 2.199.0 (#5529) --- 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 29d5da1ab..1e630bbe6 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.199.0 - 2025-09-14 ### Added diff --git a/package-lock.json b/package-lock.json index a7a9df6c7..9c5ecf656 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ghostfolio", - "version": "2.199.0-beta.0", + "version": "2.199.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ghostfolio", - "version": "2.199.0-beta.0", + "version": "2.199.0", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/package.json b/package.json index ac62690b9..855a33da9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ghostfolio", - "version": "2.199.0-beta.0", + "version": "2.199.0", "homepage": "https://ghostfol.io", "license": "AGPL-3.0", "repository": "https://github.com/ghostfolio/ghostfolio",