From 73e8fb38ad41462029d3f744daa3e251e4f9e069 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 3 Oct 2026 10:50:38 +0200 Subject: [PATCH] Fix data gathering of manual activities --- .../app/activities/activities.controller.ts | 11 +++++----- apps/api/src/helper/data-source.helper.ts | 15 ++++++++++++- .../data-gathering/data-gathering.service.ts | 22 ++++--------------- 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/apps/api/src/app/activities/activities.controller.ts b/apps/api/src/app/activities/activities.controller.ts index 1146cccda7..90851b2ca4 100644 --- a/apps/api/src/app/activities/activities.controller.ts +++ b/apps/api/src/app/activities/activities.controller.ts @@ -2,6 +2,7 @@ import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorat import { Impersonation } from '@ghostfolio/api/decorators/impersonation.decorator'; import { RequiresScope } from '@ghostfolio/api/decorators/requires-scope.decorator'; import { isActivityInFuture } from '@ghostfolio/api/helper/activity.helper'; +import { isDataGatheringSupported } from '@ghostfolio/api/helper/data-source.helper'; import { RedactValuesInResponseInterceptor } from '@ghostfolio/api/interceptors/redact-values-in-response/redact-values-in-response.interceptor'; import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.interceptor'; import { TransformDataSourceInResponseInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-response/transform-data-source-in-response.interceptor'; @@ -32,7 +33,7 @@ import { Query, UseInterceptors } from '@nestjs/common'; -import { Order } from '@prisma/client'; +import { Order, Prisma } from '@prisma/client'; import { parseISO } from 'date-fns'; import { StatusCodes, getReasonPhrase } from 'http-status-codes'; @@ -216,7 +217,7 @@ export class ActivitiesController { userId, userSubscription }: ImpersonationContext - ): Promise { + ): Promise> { // Evaluate the more restrictive subscription of the authenticated user // and the owner of the activity const subscription = @@ -285,11 +286,11 @@ export class ActivitiesController { }); if ( - activity.SymbolProfile.dataSource !== 'MANUAL' && + isDataGatheringSupported(activity.SymbolProfile) && !isActivityInFuture({ date: activity.date }) ) { - // Gather symbol data in the background, if the data source is not - // MANUAL and the date is not in the future + // Gather symbol data in the background, if the asset profile supports + // data gathering and the date is not in the future this.dataGatheringService.gatherSymbols({ dataGatheringItems: [ { diff --git a/apps/api/src/helper/data-source.helper.ts b/apps/api/src/helper/data-source.helper.ts index 79802883e8..19a26816d9 100644 --- a/apps/api/src/helper/data-source.helper.ts +++ b/apps/api/src/helper/data-source.helper.ts @@ -1,6 +1,7 @@ import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; -import { DataSource } from '@prisma/client'; +import { DataSource, Prisma } from '@prisma/client'; +import { isEmpty } from 'lodash-es'; import { createHash } from 'node:crypto'; const DATA_SOURCES_WITHOUT_ENCODING: DataSource[] = [ @@ -95,6 +96,18 @@ export function getUnmaskedGhostfolioDataSource({ : dataSource; } +export function isDataGatheringSupported({ + dataSource, + scraperConfiguration +}: { + dataSource: DataSource; + scraperConfiguration: Prisma.JsonValue; +}) { + // An asset profile with the MANUAL data source supports data gathering only + // with a scraper configuration + return dataSource !== DataSource.MANUAL || !isEmpty(scraperConfiguration); +} + export function isDataSourceEncodedInResponse(dataSource: DataSource) { return !DATA_SOURCES_WITHOUT_ENCODING.includes(dataSource); } diff --git a/apps/api/src/services/queues/data-gathering/data-gathering.service.ts b/apps/api/src/services/queues/data-gathering/data-gathering.service.ts index 3a98757591..1162143421 100644 --- a/apps/api/src/services/queues/data-gathering/data-gathering.service.ts +++ b/apps/api/src/services/queues/data-gathering/data-gathering.service.ts @@ -1,3 +1,4 @@ +import { isDataGatheringSupported } from '@ghostfolio/api/helper/data-source.helper'; import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service'; import { DataEnhancerInterface } from '@ghostfolio/api/services/data-provider/interfaces/data-enhancer.interface'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; @@ -33,7 +34,6 @@ import { Inject, Injectable, Logger } from '@nestjs/common'; import { Prisma } from '@prisma/client'; import { Job, JobOptions, Queue } from 'bull'; import { format, min, subDays, subMilliseconds, subYears } from 'date-fns'; -import { isEmpty } from 'lodash-es'; import ms, { StringValue } from 'ms'; @Injectable() @@ -506,12 +506,7 @@ export class DataGatheringService { return symbolProfiles .filter(({ dataSource, scraperConfiguration }) => { - const manualDataSourceWithScraperConfiguration = - dataSource === 'MANUAL' && !isEmpty(scraperConfiguration); - - return ( - dataSource !== 'MANUAL' || manualDataSourceWithScraperConfiguration - ); + return isDataGatheringSupported({ dataSource, scraperConfiguration }); }) .map(({ dataSource, symbol }) => { return { dataSource, symbol }; @@ -534,14 +529,10 @@ export class DataGatheringService { return symbolProfiles .filter(({ dataSource, scraperConfiguration, symbol }) => { - const manualDataSourceWithScraperConfiguration = - dataSource === 'MANUAL' && !isEmpty(scraperConfiguration); - return ( !assetProfileIdentifiersWithRecentMarketData.some((item) => { return item.dataSource === dataSource && item.symbol === symbol; - }) && - (dataSource !== 'MANUAL' || manualDataSourceWithScraperConfiguration) + }) && isDataGatheringSupported({ dataSource, scraperConfiguration }) ); }) .map((symbolProfile) => { @@ -598,12 +589,7 @@ export class DataGatheringService { }) ) .filter(({ dataSource, scraperConfiguration }) => { - const manualDataSourceWithScraperConfiguration = - dataSource === 'MANUAL' && !isEmpty(scraperConfiguration); - - return ( - dataSource !== 'MANUAL' || manualDataSourceWithScraperConfiguration - ); + return isDataGatheringSupported({ dataSource, scraperConfiguration }); }) .map((symbolProfile) => { let date = symbolProfile.activities?.[0]?.date ?? startDate;