From 8529aea25ee4f98e16ae6c8d508d361648ed1687 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 19 Sep 2026 20:35:25 +0200 Subject: [PATCH] Task/extend portfolio snapshot cache key by performance calculation type (#7913) * Extend portfolio snapshot cache key by performance calculation type * Update changelog --- CHANGELOG.md | 1 + apps/api/src/app/auth/web-auth.service.ts | 6 +++++- .../app/portfolio/calculator/portfolio-calculator.ts | 8 ++++++-- .../src/app/redis-cache/redis-cache.service.mock.ts | 5 ++++- apps/api/src/app/redis-cache/redis-cache.service.ts | 11 +++++++++-- apps/api/src/events/portfolio-changed.listener.ts | 3 ++- .../portfolio-snapshot.processor.ts | 1 + 7 files changed, 28 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f75e481fe..8b00ee0b3c 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 +- Extended the cache key of the portfolio snapshot by the performance calculation type - Hardened the validation of the device id in the biometric authentication ### Fixed diff --git a/apps/api/src/app/auth/web-auth.service.ts b/apps/api/src/app/auth/web-auth.service.ts index 6aa86a684c..40a86398a6 100644 --- a/apps/api/src/app/auth/web-auth.service.ts +++ b/apps/api/src/app/auth/web-auth.service.ts @@ -283,7 +283,11 @@ export class WebAuthService { }); const portfolioSnapshotKey = - this.redisCacheService.getPortfolioSnapshotKey({ filters, userId }); + this.redisCacheService.getPortfolioSnapshotKey({ + filters, + userId, + calculationType: userSettings.performanceCalculationType + }); if (await this.isPortfolioSnapshotExpired(portfolioSnapshotKey)) { await this.portfolioSnapshotService.addJobToQueue({ diff --git a/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts b/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts index 5f6e91b248..4cb88296cd 100644 --- a/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts +++ b/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts @@ -1208,8 +1208,12 @@ export abstract class PortfolioCalculator { let cachedPortfolioSnapshot: PortfolioSnapshot | undefined; let isCachedPortfolioSnapshotExpired = false; + + const calculationType = this.getPerformanceCalculationType(); + const portfolioSnapshotKey = this.redisCacheService.getPortfolioSnapshotKey( { + calculationType, filters: this.filters, userId: this.userId } @@ -1248,7 +1252,7 @@ export abstract class PortfolioCalculator { // Compute in the background this.portfolioSnapshotService.addJobToQueue({ data: { - calculationType: this.getPerformanceCalculationType(), + calculationType, filters: this.filters, userCurrency: this.currency, userId: this.userId @@ -1271,7 +1275,7 @@ export abstract class PortfolioCalculator { // Wait for computation await this.portfolioSnapshotService.addJobToQueue({ data: { - calculationType: this.getPerformanceCalculationType(), + calculationType, filters: this.filters, userCurrency: this.currency, userId: this.userId diff --git a/apps/api/src/app/redis-cache/redis-cache.service.mock.ts b/apps/api/src/app/redis-cache/redis-cache.service.mock.ts index 2a3c1cc7af..78ecda1c9d 100644 --- a/apps/api/src/app/redis-cache/redis-cache.service.mock.ts +++ b/apps/api/src/app/redis-cache/redis-cache.service.mock.ts @@ -1,4 +1,5 @@ import { Filter } from '@ghostfolio/common/interfaces'; +import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type'; export const RedisCacheServiceMock = { cache: new Map(), @@ -8,15 +9,17 @@ export const RedisCacheServiceMock = { return Promise.resolve(value); }, getPortfolioSnapshotKey: ({ + calculationType, filters, userId }: { + calculationType: PerformanceCalculationType; filters?: Filter[]; userId: string; }): string => { const filtersHash = filters?.length; - return `portfolio-snapshot-${userId}${filtersHash > 0 ? `-${filtersHash}` : ''}`; + return `portfolio-snapshot-${userId}-${calculationType}${filtersHash > 0 ? `-${filtersHash}` : ''}`; }, reset: () => { RedisCacheServiceMock.cache.clear(); 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 f2993520f4..857d96e945 100644 --- a/apps/api/src/app/redis-cache/redis-cache.service.ts +++ b/apps/api/src/app/redis-cache/redis-cache.service.ts @@ -1,6 +1,7 @@ import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { getAssetProfileIdentifier } from '@ghostfolio/common/helper'; import { AssetProfileIdentifier, Filter } from '@ghostfolio/common/interfaces'; +import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type'; import { CACHE_MANAGER, Cache } from '@nestjs/cache-manager'; import { Inject, Injectable, Logger } from '@nestjs/common'; @@ -52,13 +53,15 @@ export class RedisCacheService { } public getPortfolioSnapshotKey({ + calculationType, filters, userId }: { + calculationType: PerformanceCalculationType; filters?: Filter[]; userId: string; }) { - let portfolioSnapshotKey = `portfolio-snapshot-${userId}`; + let portfolioSnapshotKey = `${this.getPortfolioSnapshotKeyPrefix({ userId })}-${calculationType}`; if (filters?.length > 0) { const filtersHash = createHash('sha256') @@ -122,7 +125,7 @@ export class RedisCacheService { userId: string; }) { const keys = await this.getKeys( - `${this.getPortfolioSnapshotKey({ userId })}` + this.getPortfolioSnapshotKeyPrefix({ userId }) ); return this.cache.mdel(keys); @@ -139,4 +142,8 @@ export class RedisCacheService { ttl ?? this.configurationService.get('CACHE_TTL') ); } + + private getPortfolioSnapshotKeyPrefix({ userId }: { userId: string }) { + return `portfolio-snapshot-${userId}`; + } } diff --git a/apps/api/src/events/portfolio-changed.listener.ts b/apps/api/src/events/portfolio-changed.listener.ts index 026711b93d..9397eb0e52 100644 --- a/apps/api/src/events/portfolio-changed.listener.ts +++ b/apps/api/src/events/portfolio-changed.listener.ts @@ -80,7 +80,8 @@ export class PortfolioChangedListener { ...PORTFOLIO_SNAPSHOT_PROCESS_JOB_OPTIONS, jobId: this.redisCacheService.getPortfolioSnapshotKey({ filters, - userId + userId, + calculationType: userSettings.performanceCalculationType }), priority: PORTFOLIO_SNAPSHOT_COMPUTATION_QUEUE_PRIORITY_LOW } diff --git a/apps/api/src/services/queues/portfolio-snapshot/portfolio-snapshot.processor.ts b/apps/api/src/services/queues/portfolio-snapshot/portfolio-snapshot.processor.ts index a6fdcdd97f..adc66e6028 100644 --- a/apps/api/src/services/queues/portfolio-snapshot/portfolio-snapshot.processor.ts +++ b/apps/api/src/services/queues/portfolio-snapshot/portfolio-snapshot.processor.ts @@ -87,6 +87,7 @@ export class PortfolioSnapshotProcessor { await this.redisCacheService.set( this.redisCacheService.getPortfolioSnapshotKey({ + calculationType: job.data.calculationType, filters: job.data.filters, userId: job.data.userId }),